Skip to content

Commit fe54d04

Browse files
author
Jason Yellick
committed
Add a fileledger implementation in rawledger.
This addition supplements the existing ramledger rawledger implementation. Neither this, nor the ramledger is intended to be performant, but they are both intended to be extremely simple. In the case of this added fileledger, it is intended to allow data persistence between stopping and starting of an orderer. Ultimately, the expectation would be that for the performant case, a high performance database of some sort, such as RocksDB or other would likely be used for persisting data. https://jira.hyperledger.org/browse/FAB-326 Change-Id: Ic75bee7cd27b311b512b2c3e0e9741b40baffbf1 Signed-off-by: Jason Yellick <[email protected]>
1 parent 45bd645 commit fe54d04

File tree

2 files changed

+369
-0
lines changed

2 files changed

+369
-0
lines changed
+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
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 fileledger
18+
19+
import (
20+
"fmt"
21+
"io/ioutil"
22+
"os"
23+
24+
ab "github.com/hyperledger/fabric/orderer/atomicbroadcast"
25+
"github.com/hyperledger/fabric/orderer/rawledger"
26+
27+
"github.com/golang/protobuf/jsonpb"
28+
"github.com/op/go-logging"
29+
)
30+
31+
var logger = logging.MustGetLogger("rawledger/fileledger")
32+
var closedChan chan struct{}
33+
34+
func init() {
35+
logging.SetLevel(logging.DEBUG, "")
36+
closedChan = make(chan struct{})
37+
close(closedChan)
38+
}
39+
40+
const blockFileFormatString string = "block_%020d.json"
41+
42+
type cursor struct {
43+
fl *fileLedger
44+
blockNumber uint64
45+
}
46+
47+
type fileLedger struct {
48+
directory string
49+
fqFormatString string
50+
height uint64
51+
signal chan struct{}
52+
lastHash []byte
53+
marshaler *jsonpb.Marshaler
54+
}
55+
56+
// New creates a new instance of the file ledger
57+
func New(directory string) rawledger.ReadWriter {
58+
logger.Debugf("Initializing fileLedger at '%s'", directory)
59+
if err := os.MkdirAll(directory, 0700); err != nil {
60+
panic(err)
61+
}
62+
fl := &fileLedger{
63+
directory: directory,
64+
fqFormatString: directory + "/" + blockFileFormatString,
65+
signal: make(chan struct{}),
66+
marshaler: &jsonpb.Marshaler{Indent: " "},
67+
}
68+
genesisBlock := &ab.Block{
69+
Number: 0,
70+
PrevHash: []byte("GENESIS"),
71+
}
72+
if _, err := os.Stat(fl.blockFilename(genesisBlock.Number)); os.IsNotExist(err) {
73+
fl.writeBlock(genesisBlock)
74+
}
75+
fl.initializeBlockHeight()
76+
logger.Debugf("Initialized to block height %d with hash %x", fl.height-1, fl.lastHash)
77+
return fl
78+
}
79+
80+
// initializeBlockHeight verifies all blocks exist between 0 and the block height, and populates the lastHash
81+
func (fl *fileLedger) initializeBlockHeight() {
82+
infos, err := ioutil.ReadDir(fl.directory)
83+
if err != nil {
84+
panic(err)
85+
}
86+
nextNumber := uint64(0)
87+
for _, info := range infos {
88+
if info.IsDir() {
89+
continue
90+
}
91+
var number uint64
92+
_, err := fmt.Sscanf(info.Name(), blockFileFormatString, &number)
93+
if err != nil {
94+
continue
95+
}
96+
if number != nextNumber {
97+
panic(fmt.Errorf("Missing block %d in the chain", nextNumber))
98+
}
99+
nextNumber++
100+
}
101+
fl.height = nextNumber
102+
block, found := fl.readBlock(fl.height - 1)
103+
if !found {
104+
panic(fmt.Errorf("Block %d was in directory listing but error reading", fl.height-1))
105+
}
106+
if block == nil {
107+
panic(fmt.Errorf("Error reading block %d", fl.height-1))
108+
}
109+
fl.lastHash = block.Hash()
110+
}
111+
112+
// blockFilename returns the fully qualified path to where a block of a given number should be stored on disk
113+
func (fl *fileLedger) blockFilename(number uint64) string {
114+
return fmt.Sprintf(fl.fqFormatString, number)
115+
}
116+
117+
// writeBlock commits a block to disk
118+
func (fl *fileLedger) writeBlock(block *ab.Block) {
119+
file, err := os.Create(fl.blockFilename(block.Number))
120+
if err != nil {
121+
panic(err)
122+
}
123+
defer file.Close()
124+
err = fl.marshaler.Marshal(file, block)
125+
logger.Debugf("Wrote block %d", block.Number)
126+
if err != nil {
127+
panic(err)
128+
}
129+
130+
}
131+
132+
// readBlock returns the block or nil, and whether the block was found or not, (nil,true) generally indicates an irrecoverable problem
133+
func (fl *fileLedger) readBlock(number uint64) (*ab.Block, bool) {
134+
file, err := os.Open(fl.blockFilename(number))
135+
if err == nil {
136+
defer file.Close()
137+
block := &ab.Block{}
138+
err = jsonpb.Unmarshal(file, block)
139+
if err != nil {
140+
return nil, true
141+
}
142+
logger.Debugf("Read block %d", block.Number)
143+
return block, true
144+
}
145+
return nil, false
146+
}
147+
148+
// Height returns the highest block number in the chain, plus one
149+
func (fl *fileLedger) Height() uint64 {
150+
return fl.height
151+
}
152+
153+
// Append creates a new block and appends it to the ledger
154+
func (fl *fileLedger) Append(messages []*ab.BroadcastMessage, proof []byte) *ab.Block {
155+
block := &ab.Block{
156+
Number: fl.height,
157+
PrevHash: fl.lastHash,
158+
Messages: messages,
159+
Proof: proof,
160+
}
161+
fl.writeBlock(block)
162+
fl.height++
163+
close(fl.signal)
164+
fl.signal = make(chan struct{})
165+
return block
166+
}
167+
168+
// Iterator implements the rawledger.Reader definition
169+
func (fl *fileLedger) Iterator(startType ab.SeekInfo_StartType, specified uint64) (rawledger.Iterator, uint64) {
170+
switch startType {
171+
case ab.SeekInfo_OLDEST:
172+
return &cursor{fl: fl, blockNumber: 0}, 0
173+
case ab.SeekInfo_NEWEST:
174+
high := fl.height - 1
175+
return &cursor{fl: fl, blockNumber: high}, high
176+
case ab.SeekInfo_SPECIFIED:
177+
if specified > fl.height {
178+
return &rawledger.NotFoundErrorIterator{}, 0
179+
}
180+
return &cursor{fl: fl, blockNumber: specified}, specified
181+
}
182+
183+
// This line should be unreachable, but the compiler requires it
184+
return &rawledger.NotFoundErrorIterator{}, 0
185+
}
186+
187+
// Next blocks until there is a new block available, or returns an error if the next block is no longer retrievable
188+
func (cu *cursor) Next() (*ab.Block, ab.Status) {
189+
// This only loops once, as signal reading indicates the new block has been written
190+
for {
191+
block, found := cu.fl.readBlock(cu.blockNumber)
192+
if found {
193+
if block == nil {
194+
return nil, ab.Status_SERVICE_UNAVAILABLE
195+
}
196+
cu.blockNumber++
197+
return block, ab.Status_SUCCESS
198+
}
199+
<-cu.fl.signal
200+
}
201+
}
202+
203+
// ReadyChan returns a channel that will close when Next is ready to be called without blocking
204+
func (cu *cursor) ReadyChan() <-chan struct{} {
205+
signal := cu.fl.signal
206+
if _, err := os.Stat(cu.fl.blockFilename(cu.blockNumber)); os.IsNotExist(err) {
207+
return signal
208+
}
209+
return closedChan
210+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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 fileledger
18+
19+
import (
20+
"bytes"
21+
"io/ioutil"
22+
"os"
23+
"testing"
24+
25+
ab "github.com/hyperledger/fabric/orderer/atomicbroadcast"
26+
)
27+
28+
type testEnv struct {
29+
t *testing.T
30+
location string
31+
}
32+
33+
func initialize(t *testing.T) (*testEnv, *fileLedger) {
34+
name, err := ioutil.TempDir("", "hyperledger")
35+
if err != nil {
36+
t.Fatalf("Error creating temp dir: %s", err)
37+
}
38+
return &testEnv{location: name, t: t}, New(name).(*fileLedger)
39+
}
40+
41+
func (tev *testEnv) tearDown() {
42+
err := os.RemoveAll(tev.location)
43+
if err != nil {
44+
tev.t.Fatalf("Error tearing down env: %s", err)
45+
}
46+
}
47+
48+
func TestInitialization(t *testing.T) {
49+
tev, fl := initialize(t)
50+
defer tev.tearDown()
51+
if fl.height != 1 {
52+
t.Fatalf("Block height should be 1")
53+
}
54+
block, found := fl.readBlock(0)
55+
if block == nil || !found {
56+
t.Fatalf("Error retrieving genesis block")
57+
}
58+
if !bytes.Equal(block.Hash(), fl.lastHash) {
59+
t.Fatalf("Block hashes did no match")
60+
}
61+
}
62+
63+
func TestReinitialization(t *testing.T) {
64+
tev, ofl := initialize(t)
65+
defer tev.tearDown()
66+
ofl.Append([]*ab.BroadcastMessage{&ab.BroadcastMessage{Data: []byte("My Data")}}, nil)
67+
fl := New(tev.location).(*fileLedger)
68+
if fl.height != 2 {
69+
t.Fatalf("Block height should be 2")
70+
}
71+
block, found := fl.readBlock(1)
72+
if block == nil || !found {
73+
t.Fatalf("Error retrieving block 1")
74+
}
75+
if !bytes.Equal(block.Hash(), fl.lastHash) {
76+
t.Fatalf("Block hashes did no match")
77+
}
78+
}
79+
80+
func TestAddition(t *testing.T) {
81+
tev, fl := initialize(t)
82+
defer tev.tearDown()
83+
prevHash := fl.lastHash
84+
fl.Append([]*ab.BroadcastMessage{&ab.BroadcastMessage{Data: []byte("My Data")}}, nil)
85+
if fl.height != 2 {
86+
t.Fatalf("Block height should be 2")
87+
}
88+
block, found := fl.readBlock(1)
89+
if block == nil || !found {
90+
t.Fatalf("Error retrieving genesis block")
91+
}
92+
if !bytes.Equal(block.PrevHash, prevHash) {
93+
t.Fatalf("Block hashes did no match")
94+
}
95+
}
96+
97+
func TestRetrieval(t *testing.T) {
98+
tev, fl := initialize(t)
99+
defer tev.tearDown()
100+
fl.Append([]*ab.BroadcastMessage{&ab.BroadcastMessage{Data: []byte("My Data")}}, nil)
101+
it, num := fl.Iterator(ab.SeekInfo_OLDEST, 99)
102+
if num != 0 {
103+
t.Fatalf("Expected genesis block iterator, but got %d", num)
104+
}
105+
signal := it.ReadyChan()
106+
select {
107+
case <-signal:
108+
default:
109+
t.Fatalf("Should be ready for block read")
110+
}
111+
block, status := it.Next()
112+
if status != ab.Status_SUCCESS {
113+
t.Fatalf("Expected to successfully read the genesis block")
114+
}
115+
if block.Number != 0 {
116+
t.Fatalf("Expected to successfully retrieve the genesis block")
117+
}
118+
signal = it.ReadyChan()
119+
select {
120+
case <-signal:
121+
default:
122+
t.Fatalf("Should still be ready for block read")
123+
}
124+
block, status = it.Next()
125+
if status != ab.Status_SUCCESS {
126+
t.Fatalf("Expected to successfully read the second block")
127+
}
128+
if block.Number != 1 {
129+
t.Fatalf("Expected to successfully retrieve the second block but got block number %d", block.Number)
130+
}
131+
}
132+
133+
func TestBlockedRetrieval(t *testing.T) {
134+
tev, fl := initialize(t)
135+
defer tev.tearDown()
136+
it, num := fl.Iterator(ab.SeekInfo_SPECIFIED, 1)
137+
if num != 1 {
138+
t.Fatalf("Expected block iterator at 1, but got %d", num)
139+
}
140+
signal := it.ReadyChan()
141+
select {
142+
case <-signal:
143+
t.Fatalf("Should not be ready for block read")
144+
default:
145+
}
146+
fl.Append([]*ab.BroadcastMessage{&ab.BroadcastMessage{Data: []byte("My Data")}}, nil)
147+
select {
148+
case <-signal:
149+
default:
150+
t.Fatalf("Should now be ready for block read")
151+
}
152+
block, status := it.Next()
153+
if status != ab.Status_SUCCESS {
154+
t.Fatalf("Expected to successfully read the second block")
155+
}
156+
if block.Number != 1 {
157+
t.Fatalf("Expected to successfully retrieve the second block")
158+
}
159+
}

0 commit comments

Comments
 (0)