Skip to content

Commit c1529a4

Browse files
committed
Initial prototype of CouchDB state DB in ledgernext
https://jira.hyperledger.org/browse/FAB-728 This commit adds a transaction manager (state database) based on CouchDB, and a sample client to demonstrate/test it. KVLedger will continue to use file based block storage for the blockchain, but will use CouchDB as the state database for simulation and commit. This experimental feature is enabled via a feature toggle switch in the code (useCouchDB). CouchDB must be already installed separately. There is a script to start CouchDB in dev env and download a docker image of CouchDB if not already downloaded. Run this command anywhere inside the dev env /fabric: "couchdb start" To switch ledger to use CouchDB, update kv_ledger_config.go variable useCouchDB to true. In kv_ledger.go NewKVLedger(), you will also need to set the CouchDB connection host, port, db name, id, password if using a non-local secured CouchDB. This initial commit is only a stand alone ledger prototype and not meant for end-to-end chaincode processing. That will come in a subsequent commit. To run the sample: In CouchDB http://localhost:5984/_utils create a db named marbles_app. You can do this from your host which has port 5984 mapped to guest 5984 (assuming you have done vagrant up after https://gerrit.hyperledger.org/r/#/c/1935/ went in Oct 25th, as that changeset open up vagrant port 5984). Then run the sample as follows: /core/ledgernext/kvledger/marble_example/main$ go run marble_example.go After running the sample, you can view the marble1 document in CouchDB. Be sure to delete it in CouchDB if you'd like to re-run the sample. Change-Id: Iea4f6ad498dc0e637f0254b6f749060e0298622c Signed-off-by: denyeart <[email protected]>
1 parent 3b52a9f commit c1529a4

File tree

11 files changed

+1452
-0
lines changed

11 files changed

+1452
-0
lines changed

core/ledger/kvledger/example/committer.go

+2
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ func (c *Committer) CommitBlock(rawBlock *protos.Block2) (*protos.Block2, []*pro
3636
var validBlock *protos.Block2
3737
var invalidTxs []*protos.InvalidTransaction
3838
var err error
39+
logger.Debugf("Committer validating the block...")
3940
if validBlock, invalidTxs, err = c.ledger.RemoveInvalidTransactionsAndPrepare(rawBlock); err != nil {
4041
return nil, nil, err
4142
}
43+
logger.Debugf("Committer committing the block...")
4244
if err = c.ledger.Commit(); err != nil {
4345
return nil, nil, err
4446
}

core/ledger/kvledger/example/consenter.go

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func ConstructConsenter() *Consenter {
3232

3333
// ConstructBlock constructs a block from a list of transactions
3434
func (c *Consenter) ConstructBlock(transactions ...*protos.Transaction2) *protos.Block2 {
35+
logger.Debugf("Construct a block based on the transactions")
3536
block := &protos.Block2{}
3637
for _, tx := range transactions {
3738
txBytes, _ := proto.Marshal(tx)
+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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 example
18+
19+
import (
20+
"encoding/json"
21+
"errors"
22+
"strconv"
23+
"strings"
24+
25+
ledger "github.com/hyperledger/fabric/core/ledger"
26+
"github.com/hyperledger/fabric/protos"
27+
logging "github.com/op/go-logging"
28+
)
29+
30+
var logger = logging.MustGetLogger("example")
31+
32+
// App - a sample fund transfer app
33+
type MarbleApp struct {
34+
name string
35+
ledger ledger.ValidatedLedger
36+
}
37+
38+
// ConstructAppInstance constructs an instance of an app
39+
func ConstructMarbleAppInstance(ledger ledger.ValidatedLedger) *MarbleApp {
40+
return &MarbleApp{"marbles_app", ledger}
41+
}
42+
43+
var marbleIndexStr = "_marbleindex" //name for the key/value that will store a list of all known marbles
44+
var openTradesStr = "_opentrades" //name for the key/value that will store all open trades
45+
46+
type Marble struct {
47+
Name string `json:"asset_name"` //the fieldtags are needed to keep case from bouncing around
48+
Color string `json:"color"`
49+
Size int `json:"size"`
50+
User string `json:"owner"`
51+
Rev string `json:"_rev"`
52+
Txid string `json:"txid"`
53+
}
54+
55+
// CreateMarble simulates init transaction
56+
func (marbleApp *MarbleApp) CreateMarble(args []string) (*protos.Transaction2, error) {
57+
// 0 1 2 3
58+
// "asdf", "blue", "35", "bob"
59+
logger.Debugf("===COUCHDB=== Entering ----------CreateMarble()----------")
60+
marbleName := args[0]
61+
marbleJsonBytes, err := init_marble(args)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
var txSimulator ledger.TxSimulator
67+
if txSimulator, err = marbleApp.ledger.NewTxSimulator(); err != nil {
68+
return nil, err
69+
}
70+
defer txSimulator.Done()
71+
72+
txSimulator.SetState(marbleApp.name, marbleName, marbleJsonBytes)
73+
74+
var txSimulationResults []byte
75+
if txSimulationResults, err = txSimulator.GetTxSimulationResults(); err != nil {
76+
return nil, err
77+
}
78+
logger.Debugf("===COUCHDB=== CreateMarble() simulation done, packaging into a transaction...")
79+
tx := constructTransaction(txSimulationResults)
80+
logger.Debugf("===COUCHDB=== Exiting CreateMarble()")
81+
return tx, nil
82+
}
83+
84+
// ============================================================================================================================
85+
// Init Marble - create a new marble, store into chaincode state
86+
// ============================================================================================================================
87+
func init_marble(args []string) ([]byte, error) {
88+
var err error
89+
90+
// 0 1 2 3
91+
// "asdf", "blue", "35", "bob"
92+
if len(args) != 4 {
93+
return nil, errors.New("Incorrect number of arguments. Expecting 4")
94+
}
95+
96+
logger.Debugf("===COUCHDB=== Entering init marble")
97+
if len(args[0]) <= 0 {
98+
return nil, errors.New("1st argument must be a non-empty string")
99+
}
100+
if len(args[1]) <= 0 {
101+
return nil, errors.New("2nd argument must be a non-empty string")
102+
}
103+
if len(args[2]) <= 0 {
104+
return nil, errors.New("3rd argument must be a non-empty string")
105+
}
106+
if len(args[3]) <= 0 {
107+
return nil, errors.New("4th argument must be a non-empty string")
108+
}
109+
110+
size, err := strconv.Atoi(args[2])
111+
if err != nil {
112+
return nil, errors.New("3rd argument must be a numeric string")
113+
}
114+
115+
color := strings.ToLower(args[1])
116+
user := strings.ToLower(args[3])
117+
118+
tx := "tx000000000000001" // COUCHDB hardcode a txid for now for demo purpose
119+
marbleJson := `{"txid": "` + tx + `", "asset_name": "` + args[0] + `", "color": "` + color + `", "size": ` + strconv.Itoa(size) + `, "owner": "` + user + `"}`
120+
marbleBytes := []byte(marbleJson)
121+
122+
logger.Debugf("===COUCHDB=== Exiting init marble")
123+
return marbleBytes, nil
124+
}
125+
126+
// TransferMarble simulates transfer transaction
127+
func (marbleApp *MarbleApp) TransferMarble(args []string) (*protos.Transaction2, error) {
128+
129+
// 0 1
130+
// "name", "bob"
131+
if len(args) < 2 {
132+
return nil, errors.New("Incorrect number of arguments. Expecting 2")
133+
}
134+
marbleName := args[0]
135+
marbleNewOwner := args[1]
136+
137+
logger.Debugf("===COUCHDB=== Entering ----------TransferMarble----------")
138+
var txSimulator ledger.TxSimulator
139+
var err error
140+
if txSimulator, err = marbleApp.ledger.NewTxSimulator(); err != nil {
141+
return nil, err
142+
}
143+
defer txSimulator.Done()
144+
145+
marbleBytes, err := txSimulator.GetState(marbleApp.name, marbleName)
146+
logger.Debugf("===COUCHDB=== marbleBytes is: %v", marbleBytes)
147+
if marbleBytes != nil {
148+
jsonString := string(marbleBytes[:])
149+
logger.Debugf("===COUCHDB=== TransferMarble() Retrieved jsonString: \n %s", jsonString)
150+
}
151+
152+
theMarble := Marble{}
153+
json.Unmarshal(marbleBytes, &theMarble) //Unmarshal JSON bytes into a Marble struct
154+
155+
logger.Debugf("===COUCHDB=== theMarble after unmarshal: %v", theMarble)
156+
157+
logger.Debugf("===COUCHDB=== Setting the owner to: %s", marbleNewOwner)
158+
theMarble.User = marbleNewOwner //change the user
159+
theMarble.Txid = "tx000000000000002" // COUCHDB hardcode a txid for now for demo purpose
160+
161+
updatedMarbleBytes, _ := json.Marshal(theMarble)
162+
if updatedMarbleBytes != nil {
163+
updatedJsonString := string(updatedMarbleBytes[:])
164+
logger.Debugf("===COUCHDB=== updatedJsonString:\n %s", updatedJsonString)
165+
}
166+
err = txSimulator.SetState(marbleApp.name, marbleName, updatedMarbleBytes)
167+
if err != nil {
168+
return nil, err
169+
}
170+
171+
var txSimulationResults []byte
172+
if txSimulationResults, err = txSimulator.GetTxSimulationResults(); err != nil {
173+
return nil, err
174+
}
175+
logger.Debugf("===COUCHDB=== TransferMarble() simulation done, packaging into a transaction...")
176+
tx := constructTransaction(txSimulationResults)
177+
return tx, nil
178+
}

core/ledger/kvledger/kv_ledger.go

+20
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,16 @@ 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/kvledger/kvledgerconfig"
2728
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt"
29+
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/couchdbtxmgmt"
2830
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/lockbasedtxmgmt"
2931
"github.com/hyperledger/fabric/protos"
32+
logging "github.com/op/go-logging"
3033
)
3134

35+
var logger = logging.MustGetLogger("kvledger")
36+
3237
// Conf captures `KVLedger` configurations
3338
type Conf struct {
3439
blockStorageDir string
@@ -58,8 +63,23 @@ type KVLedger struct {
5863
// NewKVLedger constructs new `KVLedger`
5964
func NewKVLedger(conf *Conf) (*KVLedger, error) {
6065
blockStore := fsblkstorage.NewFsBlockStore(fsblkstorage.NewConf(conf.blockStorageDir, conf.maxBlockfileSize))
66+
67+
if kvledgerconfig.IsCouchDBEnabled() == true {
68+
//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
69+
logger.Debugf("===COUCHDB=== NewKVLedger() Using CouchDB instead of RocksDB...hardcoding and passing connection config for now")
70+
txmgmt := couchdbtxmgmt.NewCouchDBTxMgr(&couchdbtxmgmt.Conf{DBPath: conf.txMgrDBPath},
71+
"127.0.0.1", //couchDB host
72+
5984, //couchDB port
73+
"marbles_app", //couchDB db name
74+
"", //enter couchDB id here
75+
"") //enter couchDB pw here
76+
return &KVLedger{blockStore, txmgmt, nil}, nil
77+
}
78+
79+
// Fall back to using RocksDB lockbased transaction manager
6180
txmgmt := lockbasedtxmgmt.NewLockBasedTxMgr(&lockbasedtxmgmt.Conf{DBPath: conf.txMgrDBPath})
6281
return &KVLedger{blockStore, txmgmt, nil}, nil
82+
6383
}
6484

6585
// GetTransactionByID retrieves a transaction by id
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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 kvledgerconfig
18+
19+
// Change this feature toggle to true to use CouchDB for state database
20+
var useCouchDB = false
21+
22+
//IsCouchDBEnabled exposes the useCouchDB variable
23+
func IsCouchDBEnabled() bool {
24+
return useCouchDB
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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 main
18+
19+
import (
20+
"fmt"
21+
"os"
22+
23+
"github.com/hyperledger/fabric/core/ledger"
24+
"github.com/hyperledger/fabric/core/ledger/kvledger"
25+
"github.com/hyperledger/fabric/core/ledger/kvledger/example"
26+
"github.com/hyperledger/fabric/protos"
27+
logging "github.com/op/go-logging"
28+
)
29+
30+
var logger = logging.MustGetLogger("main")
31+
32+
const (
33+
ledgerPath = "/tmp/test/ledgernext/kvledger/example"
34+
)
35+
36+
var finalLedger ledger.ValidatedLedger
37+
var marbleApp *example.MarbleApp
38+
var committer *example.Committer
39+
var consenter *example.Consenter
40+
41+
func init() {
42+
43+
// Initialization will get a handle to the ledger at the specified path
44+
// Note, if subledgers are supported in the future,
45+
// the various ledgers could be created/managed at this level
46+
logger.Debugf("===COUCHDB=== Marble Example main init()")
47+
48+
os.RemoveAll(ledgerPath)
49+
ledgerConf := kvledger.NewConf(ledgerPath, 0)
50+
var err error
51+
finalLedger, err = kvledger.NewKVLedger(ledgerConf)
52+
if err != nil {
53+
panic(fmt.Errorf("Error in NewKVLedger(): %s", err))
54+
}
55+
marbleApp = example.ConstructMarbleAppInstance(finalLedger)
56+
committer = example.ConstructCommitter(finalLedger)
57+
consenter = example.ConstructConsenter()
58+
}
59+
60+
func main() {
61+
defer finalLedger.Close()
62+
63+
// Each of the functions here will emulate endorser, orderer,
64+
// and committer by calling ledger APIs to similate the proposal,
65+
// get simulation results, create a transaction, add it to a block,
66+
// and then commit the block.
67+
68+
initApp()
69+
transferMarble()
70+
71+
}
72+
73+
func initApp() {
74+
logger.Debugf("===COUCHDB=== Marble Example initApp() to create a marble")
75+
marble := []string{"marble1", "blue", "35", "tom"}
76+
tx, err := marbleApp.CreateMarble(marble)
77+
handleError(err, true)
78+
rawBlock := consenter.ConstructBlock(tx)
79+
finalBlock, invalidTx, err := committer.CommitBlock(rawBlock)
80+
handleError(err, true)
81+
printBlocksInfo(rawBlock, finalBlock, invalidTx)
82+
}
83+
84+
func transferMarble() {
85+
logger.Debugf("===COUCHDB=== Marble Example transferMarble()")
86+
tx1, err := marbleApp.TransferMarble([]string{"marble1", "jerry"})
87+
handleError(err, true)
88+
rawBlock := consenter.ConstructBlock(tx1)
89+
finalBlock, invalidTx, err := committer.CommitBlock(rawBlock)
90+
handleError(err, true)
91+
printBlocksInfo(rawBlock, finalBlock, invalidTx)
92+
}
93+
94+
func printBlocksInfo(rawBlock *protos.Block2, finalBlock *protos.Block2, invalidTxs []*protos.InvalidTransaction) {
95+
fmt.Printf("Num txs in rawBlock = [%d], num txs in final block = [%d], num invalidTxs = [%d]\n",
96+
len(rawBlock.Transactions), len(finalBlock.Transactions), len(invalidTxs))
97+
}
98+
99+
func handleError(err error, quit bool) {
100+
if err != nil {
101+
if quit {
102+
panic(fmt.Errorf("Error: %s\n", err))
103+
} else {
104+
fmt.Printf("Error: %s\n", err)
105+
}
106+
}
107+
}

0 commit comments

Comments
 (0)