Skip to content

Commit d18aa98

Browse files
committed
FAB-1140 Ledger History Database framework
This is an initial check-in that will 1) create the history database in couchDB if it does not exist. 2) add the history commit to the LEDGER Commit if history is enabled 3) stores the history in the history database The History functionality is not enabled unless both couchDB and History are enabled in the config. Note that tests will be added to validate the writes and the actual data written to the database in future changes. The tests to validate history is not possible until the query APIs are put in place in future changes. Change-Id: Id207007ab5faae957c1e05234e441566a116ea33 Signed-off-by: Mari Wade <[email protected]>
1 parent 82d6870 commit d18aa98

File tree

7 files changed

+277
-51
lines changed

7 files changed

+277
-51
lines changed

core/ledger/history/couchdb_histmgr.go

+97-8
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,125 @@ limitations under the License.
1717
package history
1818

1919
import (
20+
"bytes"
21+
"strconv"
22+
23+
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt"
2024
"github.com/hyperledger/fabric/core/ledger/util/couchdb"
2125
"github.com/hyperledger/fabric/protos/common"
26+
putils "github.com/hyperledger/fabric/protos/utils"
2227
logging "github.com/op/go-logging"
2328
)
2429

25-
var logger = logging.MustGetLogger("txhistorymgmt")
30+
var logger = logging.MustGetLogger("history")
2631

27-
// CouchDBHistMgr a simple implementation of interface `histmgmt.TxHistMgr'.
28-
// TODO This implementation does not currently use a lock but may need one to insure query's are consistent
32+
// CouchDBHistMgr a simple implementation of interface `histmgmt.HistMgr'.
33+
// TODO This implementation does not currently use a lock but may need one to ensure query's are consistent
2934
type CouchDBHistMgr struct {
3035
couchDB *couchdb.CouchDBConnectionDef // COUCHDB new properties for CouchDB
3136
}
3237

33-
// NewCouchDBHistMgr constructs a new `CouchDBTxHistMgr`
38+
// NewCouchDBHistMgr constructs a new `CouchDB HistMgr`
3439
func NewCouchDBHistMgr(couchDBConnectURL string, dbName string, id string, pw string) *CouchDBHistMgr {
3540

3641
//TODO locking has not been implemented but may need some sort of locking to insure queries are valid data.
3742

3843
couchDB, err := couchdb.CreateCouchDBConnectionAndDB(couchDBConnectURL, dbName, id, pw)
3944
if err != nil {
40-
logger.Errorf("Error during NewCouchDBHistMgr(): %s\n", err.Error())
45+
logger.Errorf("===HISTORYDB=== Error during NewCouchDBHistMgr(): %s\n", err.Error())
4146
return nil
4247
}
4348

44-
// db and stateIndexCF will not be used for CouchDB. TODO to cleanup
4549
return &CouchDBHistMgr{couchDB: couchDB}
4650
}
4751

48-
// Commit implements method in interface `txhistorymgmt.TxMgr`
52+
// Commit implements method in interface `histmgmt.HistMgr`
53+
// This writes to a separate history database.
4954
func (histmgr *CouchDBHistMgr) Commit(block *common.Block) error {
50-
logger.Debugf("===HISTORYDB=== Entering CouchDBTxHistMgr.Commit()")
55+
logger.Debugf("===HISTORYDB=== Entering CouchDBHistMgr.Commit()")
56+
57+
//Get the blocknumber off of the header
58+
blockNo := block.Header.Number
59+
//Set the starting tranNo to 0
60+
var tranNo uint64
61+
62+
logger.Debugf("===HISTORYDB=== Updating history for blockNo: %v with [%d] transactions",
63+
blockNo, len(block.Data.Data))
64+
for _, envBytes := range block.Data.Data {
65+
tranNo++
66+
logger.Debugf("===HISTORYDB=== Updating history for tranNo: %v", tranNo)
67+
68+
// extract actions from the envelope message
69+
respPayload, err := putils.GetActionFromEnvelope(envBytes)
70+
if err != nil {
71+
return err
72+
}
73+
74+
//preparation for extracting RWSet from transaction
75+
txRWSet := &txmgmt.TxReadWriteSet{}
76+
77+
// Get the Result from the Action and then Unmarshal
78+
// it into a TxReadWriteSet using custom unmarshalling
79+
if err = txRWSet.Unmarshal(respPayload.Results); err != nil {
80+
return err
81+
}
82+
83+
//Transactions that have data that is not JSON such as binary data,
84+
// the write value will not write to history database.
85+
//These types of transactions will have the key written to the history
86+
// database to support history key scans. We do not write the binary
87+
// value to CouchDB since the purpose of the history database value is
88+
// for query andbinary data can not be queried.
89+
for _, nsRWSet := range txRWSet.NsRWs {
90+
ns := nsRWSet.NameSpace
91+
92+
for _, kvWrite := range nsRWSet.Writes {
93+
writeKey := kvWrite.Key
94+
writeValue := kvWrite.Value
95+
compositeKey := constructCompositeKey(ns, writeKey, blockNo, tranNo)
96+
var bytesDoc []byte
97+
98+
logger.Debugf("===HISTORYDB=== ns (namespace or cc id) = %v, writeKey: %v, compositeKey: %v, writeValue = %v",
99+
ns, writeKey, compositeKey, writeValue)
100+
101+
if couchdb.IsJSON(string(writeValue)) {
102+
//logger.Debugf("===HISTORYDB=== yes JSON store writeValue = %v", string(writeValue))
103+
bytesDoc = writeValue
104+
} else {
105+
//For data that is not in JSON format only store the key
106+
//logger.Debugf("===HISTORYDB=== not JSON only store key")
107+
bytesDoc = []byte(`{}`)
108+
}
109+
110+
// SaveDoc using couchdb client and use JSON format
111+
rev, err := histmgr.couchDB.SaveDoc(compositeKey, "", bytesDoc, nil)
112+
if err != nil {
113+
logger.Errorf("===HISTORYDB=== Error during Commit(): %s\n", err.Error())
114+
return err
115+
}
116+
if rev != "" {
117+
logger.Debugf("===HISTORYDB=== Saved document revision number: %s\n", rev)
118+
}
119+
120+
}
121+
}
122+
123+
}
51124
return nil
52125
}
126+
127+
func constructCompositeKey(ns string, key string, blocknum uint64, trannum uint64) string {
128+
//History Key is: "namespace key blocknum trannum"", with namespace being the chaincode id
129+
130+
// TODO - We will likely want sortable varint encoding, rather then a simple number, in order to support sorted key scans
131+
var buffer bytes.Buffer
132+
buffer.WriteString(ns)
133+
buffer.WriteByte(0)
134+
buffer.WriteString(key)
135+
buffer.WriteByte(0)
136+
buffer.WriteString(strconv.Itoa(int(blocknum)))
137+
buffer.WriteByte(0)
138+
buffer.WriteString(strconv.Itoa(int(trannum)))
139+
140+
return buffer.String()
141+
}

core/ledger/history/couchdb_histmgr_test.go

+21-39
Original file line numberDiff line numberDiff line change
@@ -18,61 +18,35 @@ package history
1818

1919
import (
2020
"fmt"
21-
"os"
2221
"testing"
2322

2423
"github.com/hyperledger/fabric/core/ledger/ledgerconfig"
2524
"github.com/hyperledger/fabric/core/ledger/testutil"
26-
"github.com/hyperledger/fabric/core/ledger/util/couchdb"
2725
)
2826

29-
//Complex setup to test the use of couch in ledger
30-
type testEnvCouch struct {
31-
couchDBPath string
32-
couchDBAddress string
33-
couchDatabaseName string
34-
couchUsername string
35-
couchPassword string
36-
}
37-
38-
func newTestEnvCouch(t testing.TB, dbPath string, dbName string) *testEnvCouch {
39-
40-
couchDBDef := ledgerconfig.GetCouchDBDefinition()
41-
os.RemoveAll(dbPath)
42-
43-
return &testEnvCouch{
44-
couchDBPath: dbPath,
45-
couchDBAddress: couchDBDef.URL,
46-
couchDatabaseName: dbName,
47-
couchUsername: couchDBDef.Username,
48-
couchPassword: couchDBDef.Password,
49-
}
50-
}
51-
52-
func (env *testEnvCouch) cleanup() {
53-
os.RemoveAll(env.couchDBPath)
54-
//create a new connection
55-
couchDB, _ := couchdb.CreateConnectionDefinition(env.couchDBAddress, env.couchDatabaseName, env.couchUsername, env.couchPassword)
56-
//drop the test database if it already existed
57-
couchDB.DropDatabase()
58-
}
27+
/*
28+
Note that these test are only run if HistoryDB is explitily enabled
29+
otherwise HistoryDB may not be installed and all the tests would fail
30+
*/
5931

60-
// couchdb_test.go tests couchdb functions already. This test just tests that a CouchDB history database is auto-created
61-
// upon creating a new history transaction manager
32+
// Note that the couchdb_test.go tests couchdb functions already. This test just tests that a
33+
// CouchDB history database is auto-created upon creating a new history manager
6234
func TestHistoryDatabaseAutoCreate(t *testing.T) {
6335

6436
//call a helper method to load the core.yaml
6537
testutil.SetupCoreYAMLConfig("./../../../peer")
38+
logger.Debugf("===HISTORYDB=== TestHistoryDatabaseAutoCreate IsCouchDBEnabled()value: %v , IsHistoryDBEnabled()value: %v\n",
39+
ledgerconfig.IsCouchDBEnabled(), ledgerconfig.IsHistoryDBEnabled())
6640

67-
//Only run the tests if CouchDB is explitily enabled in the code,
68-
//otherwise CouchDB may not be installed and all the tests would fail
69-
//TODO replace this with external config property rather than config within the code
70-
if ledgerconfig.IsCouchDBEnabled() == true {
41+
if ledgerconfig.IsHistoryDBEnabled() == true {
7142

72-
env := newTestEnvCouch(t, "/tmp/tests/ledger/history", "history-test")
43+
env := newTestEnvHistoryCouchDB(t, "history-test")
7344
env.cleanup() //cleanup at the beginning to ensure the database doesn't exist already
7445
defer env.cleanup() //and cleanup at the end
7546

47+
logger.Debugf("===HISTORYDB=== env.couchDBAddress: %v , env.couchDatabaseName: %v env.couchUsername: %v env.couchPassword: %v\n",
48+
env.couchDBAddress, env.couchDatabaseName, env.couchUsername, env.couchPassword)
49+
7650
histMgr := NewCouchDBHistMgr(
7751
env.couchDBAddress, //couchDB Address
7852
env.couchDatabaseName, //couchDB db name
@@ -98,5 +72,13 @@ func TestHistoryDatabaseAutoCreate(t *testing.T) {
9872
testutil.AssertEquals(t, dbResp2.DbName, env.couchDatabaseName)
9973

10074
}
75+
}
76+
77+
func TestConstructCompositeKey(t *testing.T) {
78+
compositeKey := constructCompositeKey("ns1", "key1", 1, 1)
79+
80+
var compositeKeySep = []byte{0x00}
81+
var strKeySep = string(compositeKeySep)
10182

83+
testutil.AssertEquals(t, compositeKey, "ns1"+strKeySep+"key1"+strKeySep+"1"+strKeySep+"1")
10284
}

core/ledger/history/histmgmt.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package history
1818

1919
import "github.com/hyperledger/fabric/protos/common"
2020

21-
// TxHistMgr - an interface that a transaction history manager should implement
22-
type TxHistMgr interface {
21+
// HistMgr - an interface that a history manager should implement
22+
type HistMgr interface {
2323
Commit(block *common.Block) error
2424
}

core/ledger/history/pkg_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright IBM Corp. 2016 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package history
18+
19+
import (
20+
"testing"
21+
22+
"github.com/hyperledger/fabric/core/ledger/ledgerconfig"
23+
"github.com/hyperledger/fabric/core/ledger/util/couchdb"
24+
)
25+
26+
//Complex setup to test the use of couch in ledger
27+
type testEnvHistoryCouchDB struct {
28+
couchDBAddress string
29+
couchDatabaseName string
30+
couchUsername string
31+
couchPassword string
32+
}
33+
34+
func newTestEnvHistoryCouchDB(t testing.TB, dbName string) *testEnvHistoryCouchDB {
35+
36+
couchDBDef := ledgerconfig.GetCouchDBDefinition()
37+
38+
return &testEnvHistoryCouchDB{
39+
couchDBAddress: couchDBDef.URL,
40+
couchDatabaseName: dbName,
41+
couchUsername: couchDBDef.Username,
42+
couchPassword: couchDBDef.Password,
43+
}
44+
}
45+
46+
func (env *testEnvHistoryCouchDB) cleanup() {
47+
48+
//create a new connection
49+
couchDB, err := couchdb.CreateConnectionDefinition(env.couchDBAddress, env.couchDatabaseName, env.couchUsername, env.couchPassword)
50+
if err == nil {
51+
//drop the test database if it already existed
52+
couchDB.DropDatabase()
53+
}
54+
}

core/ledger/kvledger/kv_ledger.go

+29-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/hyperledger/fabric/core/ledger"
2525
"github.com/hyperledger/fabric/core/ledger/blkstorage"
2626
"github.com/hyperledger/fabric/core/ledger/blkstorage/fsblkstorage"
27+
"github.com/hyperledger/fabric/core/ledger/history"
2728
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt"
2829
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/couchdbtxmgmt"
2930
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/lockbasedtxmgmt"
@@ -60,6 +61,7 @@ func NewConf(filesystemPath string, maxBlockfileSize int) *Conf {
6061
type KVLedger struct {
6162
blockStore blkstorage.BlockStore
6263
txtmgmt txmgmt.TxMgr
64+
historymgmt history.HistMgr
6365
pendingBlockToCommit *common.Block
6466
}
6567

@@ -77,7 +79,10 @@ func NewKVLedger(conf *Conf) (*KVLedger, error) {
7779
blockStorageConf := fsblkstorage.NewConf(conf.blockStorageDir, conf.maxBlockfileSize)
7880
blockStore := fsblkstorage.NewFsBlockStore(blockStorageConf, indexConfig)
7981

82+
//State and History database managers
8083
var txmgmt txmgmt.TxMgr
84+
var historymgmt history.HistMgr
85+
8186
if ledgerconfig.IsCouchDBEnabled() == true {
8287
//By default we can talk to CouchDB with empty id and pw (""), or you can add your own id and password to talk to a secured CouchDB
8388
logger.Debugf("===COUCHDB=== NewKVLedger() Using CouchDB instead of RocksDB...hardcoding and passing connection config for now")
@@ -94,14 +99,26 @@ func NewKVLedger(conf *Conf) (*KVLedger, error) {
9499
// Fall back to using RocksDB lockbased transaction manager
95100
txmgmt = lockbasedtxmgmt.NewLockBasedTxMgr(&lockbasedtxmgmt.Conf{DBPath: conf.txMgrDBPath})
96101
}
97-
l := &KVLedger{blockStore, txmgmt, nil}
102+
103+
if ledgerconfig.IsHistoryDBEnabled() == true {
104+
logger.Debugf("===HISTORYDB=== NewKVLedger() Using CouchDB for transaction history database")
105+
106+
couchDBDef := ledgerconfig.GetCouchDBDefinition()
107+
108+
historymgmt = history.NewCouchDBHistMgr(
109+
couchDBDef.URL, //couchDB connection URL
110+
"system_history", //couchDB db name matches ledger name, TODO for now use system_history ledger, eventually allow passing in subledger name
111+
couchDBDef.Username, //enter couchDB id here
112+
couchDBDef.Password) //enter couchDB pw here
113+
}
114+
115+
l := &KVLedger{blockStore, txmgmt, historymgmt, nil}
98116

99117
if err := recoverStateDB(l); err != nil {
100118
panic(fmt.Errorf(`Error during state DB recovery:%s`, err))
101119
}
102120

103121
return l, nil
104-
105122
}
106123

107124
//Recover the state database by recommitting last valid blocks
@@ -220,6 +237,16 @@ func (l *KVLedger) Commit() error {
220237
if err := l.txtmgmt.Commit(); err != nil {
221238
panic(fmt.Errorf(`Error during commit to txmgr:%s`, err))
222239
}
240+
241+
//TODO future will want to run async with state db writes. History needs to wait for chain (FSBlock) to write but not the state db
242+
logger.Debugf("===HISTORYDB=== Commit() will write to hisotry if enabled else will be by-passed if not enabled: vledgerconfig.IsHistoryDBEnabled(): %v\n", ledgerconfig.IsHistoryDBEnabled())
243+
if ledgerconfig.IsHistoryDBEnabled() == true {
244+
logger.Debugf("Committing transactions to history database")
245+
if err := l.historymgmt.Commit(l.pendingBlockToCommit); err != nil {
246+
panic(fmt.Errorf(`Error during commit to txthistory:%s`, err))
247+
}
248+
}
249+
223250
l.pendingBlockToCommit = nil
224251
return nil
225252
}

0 commit comments

Comments
 (0)