OSDN Git Service

Merge branch 'bvm' into demo
[bytom/bytom.git] / protocol / block.go
1 package protocol
2
3 import (
4         "context"
5         "time"
6
7         "github.com/bytom/errors"
8         "github.com/bytom/log"
9         "github.com/bytom/protocol/bc"
10         "github.com/bytom/protocol/bc/legacy"
11         "github.com/bytom/protocol/state"
12         "github.com/bytom/protocol/validation"
13 )
14
15 // maxBlockTxs limits the number of transactions
16 // included in each block.
17 const maxBlockTxs = 10000
18
19 // saveSnapshotFrequency stores how often to save a state
20 // snapshot to the Store.
21 const saveSnapshotFrequency = time.Hour
22
23 var (
24         // ErrBadBlock is returned when a block is invalid.
25         ErrBadBlock = errors.New("invalid block")
26
27         // ErrStaleState is returned when the Chain does not have a current
28         // blockchain state.
29         ErrStaleState = errors.New("stale blockchain state")
30
31         // ErrBadStateRoot is returned when the computed assets merkle root
32         // disagrees with the one declared in a block header.
33         ErrBadStateRoot = errors.New("invalid state merkle root")
34 )
35
36 // GetBlock returns the block at the given height, if there is one,
37 // otherwise it returns an error.
38 func (c *Chain) GetBlock(height uint64) (*legacy.Block, error) {
39         return c.store.GetBlock(height)
40 }
41
42 // ValidateBlock validates an incoming block in advance of applying it
43 // to a snapshot (with ApplyValidBlock) and committing it to the
44 // blockchain (with CommitAppliedBlock).
45 func (c *Chain) ValidateBlock(block, prev *legacy.Block) error {
46         blockEnts := legacy.MapBlock(block)
47         prevEnts := legacy.MapBlock(prev)
48         err := validation.ValidateBlock(blockEnts, prevEnts)
49         if err != nil {
50                 return errors.Sub(ErrBadBlock, err)
51         }
52         return errors.Sub(ErrBadBlock, err)
53 }
54
55 // ApplyValidBlock creates an updated snapshot without validating the
56 // block.
57 func (c *Chain) ApplyValidBlock(block *legacy.Block) (*state.Snapshot, error) {
58         //TODO replace with a pre-defined init blo
59         var newSnapshot *state.Snapshot
60         if c.state.snapshot == nil {
61                 newSnapshot = state.Empty()
62         } else {
63                 newSnapshot = state.Copy(c.state.snapshot)
64         }
65
66         err := newSnapshot.ApplyBlock(legacy.MapBlock(block))
67         if err != nil {
68                 return nil, err
69         }
70         //fmt.Printf("want %v, ger %v \n", block.BlockHeader.AssetsMerkleRoot, newSnapshot.Tree.RootHash())
71         if block.AssetsMerkleRoot != newSnapshot.Tree.RootHash() {
72                 return nil, ErrBadStateRoot
73         }
74         return newSnapshot, nil
75 }
76
77 // CommitBlock commits a block to the blockchain. The block
78 // must already have been applied with ApplyValidBlock or
79 // ApplyNewBlock, which will have produced the new snapshot that's
80 // required here.
81 //
82 // This function saves the block to the store and sometimes (not more
83 // often than saveSnapshotFrequency) saves the state tree to the
84 // store. New-block callbacks (via asynchronous block-processor pins)
85 // are triggered.
86 //
87 // TODO(bobg): rename to CommitAppliedBlock for clarity (deferred from https://github.com/chain/chain/pull/788)
88 func (c *Chain) CommitAppliedBlock(ctx context.Context, block *legacy.Block, snapshot *state.Snapshot) error {
89         // SaveBlock is the linearization point. Once the block is committed
90         // to persistent storage, the block has been applied and everything
91         // else can be derived from that block.
92         err := c.store.SaveBlock(block)
93         if err != nil {
94                 return errors.Wrap(err, "storing block")
95         }
96         if block.Time().After(c.lastQueuedSnapshot.Add(saveSnapshotFrequency)) {
97                 c.queueSnapshot(ctx, block.Height, block.Time(), snapshot)
98         }
99
100         err = c.store.FinalizeBlock(ctx, block.Height)
101         if err != nil {
102                 return errors.Wrap(err, "finalizing block")
103         }
104
105         // c.setState will update the local blockchain state and height.
106         // When c.store is a txdb.Store, and c has been initialized with a
107         // channel from txdb.ListenBlocks, then the above call to
108         // c.store.FinalizeBlock will have done a postgresql NOTIFY and
109         // that will wake up the goroutine in NewChain, which also calls
110         // setHeight.  But duplicate calls with the same blockheight are
111         // harmless; and the following call is required in the cases where
112         // it's not redundant.
113         c.setState(block, snapshot)
114         return nil
115 }
116
117 func (c *Chain) queueSnapshot(ctx context.Context, height uint64, timestamp time.Time, s *state.Snapshot) {
118         // Non-blockingly queue the snapshot for storage.
119         ps := pendingSnapshot{height: height, snapshot: s}
120         select {
121         case c.pendingSnapshots <- ps:
122                 c.lastQueuedSnapshot = timestamp
123         default:
124                 // Skip it; saving snapshots is taking longer than the snapshotting period.
125                 log.Printf(ctx, "snapshot storage is taking too long; last queued at %s",
126                         c.lastQueuedSnapshot)
127         }
128 }
129
130 func (c *Chain) setHeight(h uint64) {
131         // We call setHeight from two places independently:
132         // CommitBlock and the Postgres LISTEN goroutine.
133         // This means we can get here twice for each block,
134         // and any of them might be arbitrarily delayed,
135         // which means h might be from the past.
136         // Detect and discard these duplicate calls.
137
138         c.state.cond.L.Lock()
139         defer c.state.cond.L.Unlock()
140
141         if h <= c.state.height {
142                 return
143         }
144         c.state.height = h
145         c.state.cond.Broadcast()
146 }
147
148 func NewInitialBlock(timestamp time.Time) (*legacy.Block, error) {
149         // TODO(kr): move this into a lower-level package (e.g. chain/protocol/bc)
150         // so that other packages (e.g. chain/protocol/validation) unit tests can
151         // call this function.
152         root, err := bc.MerkleRoot(nil) // calculate the zero value of the tx merkle root
153         if err != nil {
154                 return nil, errors.Wrap(err, "calculating zero value of tx merkle root")
155         }
156
157         b := &legacy.Block{
158                 BlockHeader: legacy.BlockHeader{
159                         Version:     1,
160                         Height:      1,
161                         TimestampMS: bc.Millis(timestamp),
162                         BlockCommitment: legacy.BlockCommitment{
163                                 TransactionsMerkleRoot: root,
164                         },
165                 },
166                 Transactions: []*legacy.Tx{},
167         }
168         return b, nil
169 }