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