From: Poseidon Date: Tue, 8 Jun 2021 07:15:42 +0000 (+0800) Subject: fix casper rollback (#1951) X-Git-Tag: bytom2-prerelease~1^2~122 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=2f24424ff9ca177437a9b1a153f504ad538ae262;p=bytom%2Fbytom.git fix casper rollback (#1951) * fix casper rollback * opt code Co-authored-by: Paladz --- diff --git a/database/store.go b/database/store.go index b46f4cba..1a4594b0 100644 --- a/database/store.go +++ b/database/store.go @@ -231,7 +231,7 @@ func (s *Store) LoadBlockIndex(stateBestHeight uint64) (*state.BlockIndex, error } // SaveChainStatus save the core's newest status && delete old status -func (s *Store) SaveChainStatus(node *state.BlockNode, view *state.UtxoViewpoint, contractView *state.ContractViewpoint, checkpoints []*state.Checkpoint, finalizedHeight uint64, finalizedHash *bc.Hash) error { +func (s *Store) SaveChainStatus(node *state.BlockNode, view *state.UtxoViewpoint, contractView *state.ContractViewpoint, finalizedHeight uint64, finalizedHash *bc.Hash) error { batch := s.db.NewBatch() if err := saveUtxoView(batch, view); err != nil { return err @@ -245,10 +245,6 @@ func (s *Store) SaveChainStatus(node *state.BlockNode, view *state.UtxoViewpoint return err } - if err := s.saveCheckpoints(batch, checkpoints); err != nil { - return err - } - bytes, err := json.Marshal(protocol.BlockStoreState{Height: node.Height, Hash: &node.Hash, FinalizedHeight: finalizedHeight, FinalizedHash: finalizedHash}) if err != nil { return err @@ -346,12 +342,13 @@ func (s *Store) SaveCheckpoints(checkpoints []*state.Checkpoint) error { func (s *Store) saveCheckpoints(batch dbm.Batch, checkpoints []*state.Checkpoint) error { for _, checkpoint := range checkpoints { + startTime := time.Now() data, err := json.Marshal(checkpoint) if err != nil { return err } - if checkpoint.Height % state.BlocksOfEpoch != 1 { + if checkpoint.Height%state.BlocksOfEpoch != 1 { header, err := s.GetBlockHeader(&checkpoint.Hash) if err != nil { return err @@ -361,6 +358,13 @@ func (s *Store) saveCheckpoints(batch dbm.Batch, checkpoints []*state.Checkpoint } batch.Set(calcCheckpointKey(checkpoint.Height, &checkpoint.Hash), data) + log.WithFields(log.Fields{ + "module": logModule, + "height": checkpoint.Height, + "hash": checkpoint.Hash.String(), + "status": checkpoint.Status, + "duration": time.Since(startTime), + }).Info("checkpoint saved on disk") } return nil } diff --git a/database/store_test.go b/database/store_test.go index 10cf0d1f..ba91edc0 100644 --- a/database/store_test.go +++ b/database/store_test.go @@ -151,7 +151,7 @@ func TestSaveChainStatus(t *testing.T) { } contractView := state.NewContractViewpoint() - if err := store.SaveChainStatus(node, view, contractView, nil,0, &bc.Hash{}); err != nil { + if err := store.SaveChainStatus(node, view, contractView,0, &bc.Hash{}); err != nil { t.Fatal(err) } diff --git a/netsync/chainmgr/block_keeper.go b/netsync/chainmgr/block_keeper.go index be274363..199c8910 100644 --- a/netsync/chainmgr/block_keeper.go +++ b/netsync/chainmgr/block_keeper.go @@ -197,9 +197,9 @@ func (bk *blockKeeper) start() { func (bk *blockKeeper) checkSyncType() int { bestHeight := bk.chain.BestBlockHeight() - peer := bk.peers.BestIrreversiblePeer(consensus.SFFullNode | consensus.SFFastSync) + peer := bk.peers.BestPeer(consensus.SFFullNode | consensus.SFFastSync) if peer != nil { - if peerIrreversibleHeight := peer.IrreversibleHeight(); peerIrreversibleHeight >= bestHeight+minGapStartFastSync { + if peerJustifiedHeight := peer.JustifiedHeight(); peerJustifiedHeight >= bestHeight+minGapStartFastSync { bk.fastSync.setSyncPeer(peer) return fastSyncType } @@ -253,7 +253,7 @@ func (bk *blockKeeper) syncWorker() { continue } - if err := bk.peers.BroadcastNewStatus(bk.chain.BestBlockHeader(), bk.chain.LastIrreversibleHeader()); err != nil { + if err := bk.peers.BroadcastNewStatus(bk.chain.BestBlockHeader(), bk.chain.LastJustifiedHeader()); err != nil { log.WithFields(log.Fields{"module": logModule, "err": err}).Error("fail on syncWorker broadcast new status") } case <-bk.quit: diff --git a/netsync/chainmgr/block_keeper_test.go b/netsync/chainmgr/block_keeper_test.go index 192db69d..ccbd5609 100644 --- a/netsync/chainmgr/block_keeper_test.go +++ b/netsync/chainmgr/block_keeper_test.go @@ -98,7 +98,7 @@ func TestCheckSyncType(t *testing.T) { for _, syncPeer := range c.peers { blockKeeper.peers.AddPeer(syncPeer.peer) blockKeeper.peers.SetStatus(syncPeer.peer.id, syncPeer.bestHeight, nil) - blockKeeper.peers.SetIrreversibleStatus(syncPeer.peer.id, syncPeer.irreversibleHeight, nil) + blockKeeper.peers.SetJustifiedStatus(syncPeer.peer.id, syncPeer.irreversibleHeight, nil) } gotType := blockKeeper.checkSyncType() if c.syncType != gotType { diff --git a/netsync/chainmgr/fast_sync_test.go b/netsync/chainmgr/fast_sync_test.go index 1deb7bbb..3f5bc6a5 100644 --- a/netsync/chainmgr/fast_sync_test.go +++ b/netsync/chainmgr/fast_sync_test.go @@ -356,7 +356,7 @@ func TestCreateFetchBlocksTasks(t *testing.T) { for _, syncPeer := range c.peers { peers.AddPeer(syncPeer.peer) peers.SetStatus(syncPeer.peer.id, syncPeer.bestHeight, nil) - peers.SetIrreversibleStatus(syncPeer.peer.id, syncPeer.irreversibleHeight, nil) + peers.SetJustifiedStatus(syncPeer.peer.id, syncPeer.irreversibleHeight, nil) } mockChain := mock.NewChain() fs := newFastSync(mockChain, &mockFetcher{baseChain: baseChain, peerStatus: peerStatus, testType: c.testType}, nil, peers) diff --git a/netsync/chainmgr/handle.go b/netsync/chainmgr/handle.go index 464471a0..c857accb 100644 --- a/netsync/chainmgr/handle.go +++ b/netsync/chainmgr/handle.go @@ -27,7 +27,7 @@ const ( // Chain is the interface for Bytom core type Chain interface { BestBlockHeader() *types.BlockHeader - LastIrreversibleHeader() *types.BlockHeader + LastJustifiedHeader() *types.BlockHeader BestBlockHeight() uint64 GetBlockByHash(*bc.Hash) (*types.Block, error) GetBlockByHeight(uint64) (*types.Block, error) @@ -249,7 +249,7 @@ func (m *Manager) handleHeadersMsg(peer *peers.Peer, msg *msgs.HeadersMessage) { func (m *Manager) handleStatusMsg(basePeer peers.BasePeer, msg *msgs.StatusMessage) { if peer := m.peers.GetPeer(basePeer.ID()); peer != nil { peer.SetBestStatus(msg.BestHeight, msg.GetBestHash()) - peer.SetIrreversibleStatus(msg.IrreversibleHeight, msg.GetIrreversibleHash()) + peer.SetJustifiedStatus(msg.JustifiedHeight, msg.GetIrreversibleHash()) } } @@ -371,7 +371,7 @@ func (m *Manager) SendStatus(peer peers.BasePeer) error { return errors.New("invalid peer") } - if err := p.SendStatus(m.chain.BestBlockHeader(), m.chain.LastIrreversibleHeader()); err != nil { + if err := p.SendStatus(m.chain.BestBlockHeader(), m.chain.LastJustifiedHeader()); err != nil { m.peers.RemovePeer(p.ID()) return err } diff --git a/netsync/messages/chain_msg.go b/netsync/messages/chain_msg.go index 10b0f19b..167449fe 100644 --- a/netsync/messages/chain_msg.go +++ b/netsync/messages/chain_msg.go @@ -270,19 +270,19 @@ func (m *BlocksMessage) String() string { //StatusResponseMessage get status response msg type StatusMessage struct { - BestHeight uint64 - BestHash [32]byte - IrreversibleHeight uint64 - IrreversibleHash [32]byte + BestHeight uint64 + BestHash [32]byte + JustifiedHeight uint64 + JustifiedHash [32]byte } //NewStatusResponseMessage construct get status response msg -func NewStatusMessage(bestHeader, irreversibleHeader *types.BlockHeader) *StatusMessage { +func NewStatusMessage(bestHeader, justifiedHeader *types.BlockHeader) *StatusMessage { return &StatusMessage{ - BestHeight: bestHeader.Height, - BestHash: bestHeader.Hash().Byte32(), - IrreversibleHeight: irreversibleHeader.Height, - IrreversibleHash: irreversibleHeader.Hash().Byte32(), + BestHeight: bestHeader.Height, + BestHash: bestHeader.Hash().Byte32(), + JustifiedHeight: justifiedHeader.Height, + JustifiedHash: justifiedHeader.Hash().Byte32(), } } @@ -293,12 +293,12 @@ func (m *StatusMessage) GetBestHash() *bc.Hash { } func (m *StatusMessage) GetIrreversibleHash() *bc.Hash { - hash := bc.NewHash(m.IrreversibleHash) + hash := bc.NewHash(m.JustifiedHash) return &hash } func (m *StatusMessage) String() string { - return fmt.Sprintf("{best hash: %s, irreversible hash: %s}", hex.EncodeToString(m.BestHash[:]), hex.EncodeToString(m.IrreversibleHash[:])) + return fmt.Sprintf("{best hash: %s, irreversible hash: %s}", hex.EncodeToString(m.BestHash[:]), hex.EncodeToString(m.JustifiedHash[:])) } //TransactionMessage notify new tx msg diff --git a/netsync/peers/peer.go b/netsync/peers/peer.go index 15fdcca7..c343538b 100644 --- a/netsync/peers/peer.go +++ b/netsync/peers/peer.go @@ -77,17 +77,17 @@ type PeerInfo struct { type Peer struct { BasePeer - mtx sync.RWMutex - services consensus.ServiceFlag - bestHeight uint64 - bestHash *bc.Hash - irreversibleHeight uint64 - irreversibleHash *bc.Hash - knownTxs *set.Set // Set of transaction hashes known to be known by this peer - knownBlocks *set.Set // Set of block hashes known to be known by this peer - knownSignatures *set.Set // Set of block signatures known to be known by this peer - knownStatus uint64 // Set of chain status known to be known by this peer - filterAdds *set.Set // Set of addresses that the spv node cares about. + mtx sync.RWMutex + services consensus.ServiceFlag + bestHeight uint64 + bestHash *bc.Hash + justifiedHeight uint64 + justifiedHash *bc.Hash + knownTxs *set.Set // Set of transaction hashes known to be known by this peer + knownBlocks *set.Set // Set of block hashes known to be known by this peer + knownSignatures *set.Set // Set of block signatures known to be known by this peer + knownStatus uint64 // Set of chain status known to be known by this peer + filterAdds *set.Set // Set of addresses that the spv node cares about. } func newPeer(basePeer BasePeer) *Peer { @@ -108,11 +108,11 @@ func (p *Peer) Height() uint64 { return p.bestHeight } -func (p *Peer) IrreversibleHeight() uint64 { +func (p *Peer) JustifiedHeight() uint64 { p.mtx.RLock() defer p.mtx.RUnlock() - return p.irreversibleHeight + return p.justifiedHeight } func (p *Peer) AddFilterAddress(address []byte) { @@ -342,8 +342,8 @@ func (p *Peer) SendTransactions(txs []*types.Tx) error { return nil } -func (p *Peer) SendStatus(bestHeader, irreversibleHeader *types.BlockHeader) error { - msg := msgs.NewStatusMessage(bestHeader, irreversibleHeader) +func (p *Peer) SendStatus(bestHeader, justifiedHeader *types.BlockHeader) error { + msg := msgs.NewStatusMessage(bestHeader, justifiedHeader) if ok := p.TrySend(msgs.BlockchainChannel, struct{ msgs.BlockchainMessage }{msg}); !ok { return errSendStatusMsg } @@ -359,12 +359,12 @@ func (p *Peer) SetBestStatus(bestHeight uint64, bestHash *bc.Hash) { p.bestHash = bestHash } -func (p *Peer) SetIrreversibleStatus(irreversibleHeight uint64, irreversibleHash *bc.Hash) { +func (p *Peer) SetJustifiedStatus(justifiedHeight uint64, justifiedHash *bc.Hash) { p.mtx.Lock() defer p.mtx.Unlock() - p.irreversibleHeight = irreversibleHeight - p.irreversibleHash = irreversibleHash + p.justifiedHeight = justifiedHeight + p.justifiedHash = justifiedHash } type PeerSet struct { @@ -416,23 +416,9 @@ func (ps *PeerSet) BestPeer(flag consensus.ServiceFlag) *Peer { if !p.services.IsEnable(flag) { continue } - if bestPeer == nil || p.bestHeight > bestPeer.bestHeight || (p.bestHeight == bestPeer.bestHeight && p.IsLAN()) { - bestPeer = p - } - } - return bestPeer -} - -func (ps *PeerSet) BestIrreversiblePeer(flag consensus.ServiceFlag) *Peer { - ps.mtx.RLock() - defer ps.mtx.RUnlock() - - var bestPeer *Peer - for _, p := range ps.peers { - if !p.services.IsEnable(flag) { - continue - } - if bestPeer == nil || p.irreversibleHeight > bestPeer.irreversibleHeight || (p.irreversibleHeight == bestPeer.irreversibleHeight && p.IsLAN()) { + if bestPeer == nil || p.JustifiedHeight() > bestPeer.JustifiedHeight() || + (p.JustifiedHeight() == bestPeer.JustifiedHeight() && p.bestHeight > bestPeer.bestHeight) || + (p.JustifiedHeight() == bestPeer.JustifiedHeight() && p.bestHeight == bestPeer.bestHeight && p.IsLAN()) { bestPeer = p } } @@ -474,8 +460,8 @@ func (ps *PeerSet) BroadcastMsg(bm BroadcastMsg) error { return nil } -func (ps *PeerSet) BroadcastNewStatus(bestHeader, irreversibleHeader *types.BlockHeader) error { - msg := msgs.NewStatusMessage(bestHeader, irreversibleHeader) +func (ps *PeerSet) BroadcastNewStatus(bestHeader, justifiedHeader *types.BlockHeader) error { + msg := msgs.NewStatusMessage(bestHeader, justifiedHeader) peers := ps.peersWithoutNewStatus(bestHeader.Height) for _, peer := range peers { if ok := peer.TrySend(msgs.BlockchainChannel, struct{ msgs.BlockchainMessage }{msg}); !ok { @@ -648,11 +634,11 @@ func (ps *PeerSet) SetStatus(peerID string, height uint64, hash *bc.Hash) { peer.SetBestStatus(height, hash) } -func (ps *PeerSet) SetIrreversibleStatus(peerID string, height uint64, hash *bc.Hash) { +func (ps *PeerSet) SetJustifiedStatus(peerID string, height uint64, hash *bc.Hash) { peer := ps.GetPeer(peerID) if peer == nil { return } - peer.SetIrreversibleStatus(height, hash) + peer.SetJustifiedStatus(height, hash) } diff --git a/netsync/peers/peer_test.go b/netsync/peers/peer_test.go index b49cdbc8..41a903f2 100644 --- a/netsync/peers/peer_test.go +++ b/netsync/peers/peer_test.go @@ -86,8 +86,8 @@ func TestSetIrreversibleStatus(t *testing.T) { peer := newPeer(&basePeer{}) height := uint64(100) hash := bc.NewHash([32]byte{0x1, 0x2}) - peer.SetIrreversibleStatus(height, &hash) - if peer.IrreversibleHeight() != height { + peer.SetJustifiedStatus(height, &hash) + if peer.JustifiedHeight() != height { t.Fatalf("test set Irreversible status err. got %d want %d", peer.Height(), height) } } @@ -341,12 +341,12 @@ func TestIrreversibleStatus(t *testing.T) { ps.AddPeer(&basePeer{id: peer2ID, serviceFlag: consensus.SFFullNode}) ps.AddPeer(&basePeer{id: peer3ID, serviceFlag: consensus.SFFastSync}) ps.AddPeer(&basePeer{id: peer4ID, serviceFlag: consensus.SFFastSync, isLan: true}) - ps.SetIrreversibleStatus(peer1ID, 1000, &block1000Hash) - ps.SetIrreversibleStatus(peer2ID, 2000, &block2000Hash) - ps.SetIrreversibleStatus(peer3ID, 3000, &block3000Hash) - ps.SetIrreversibleStatus(peer4ID, 3000, &block3000Hash) + ps.SetJustifiedStatus(peer1ID, 1000, &block1000Hash) + ps.SetJustifiedStatus(peer2ID, 2000, &block2000Hash) + ps.SetJustifiedStatus(peer3ID, 3000, &block3000Hash) + ps.SetJustifiedStatus(peer4ID, 3000, &block3000Hash) targetPeer := peer4ID - peer := ps.BestIrreversiblePeer(consensus.SFFastSync) + peer := ps.BestPeer(consensus.SFFastSync) if peer.ID() != targetPeer { t.Fatalf("test set status err. Name of target peer %s got %s", peer4ID, peer.ID()) diff --git a/protocol/apply_block.go b/protocol/apply_block.go index 7b494669..acb0e4d1 100644 --- a/protocol/apply_block.go +++ b/protocol/apply_block.go @@ -3,6 +3,8 @@ package protocol import ( "encoding/hex" + "github.com/sirupsen/logrus" + "github.com/bytom/bytom/config" "github.com/bytom/bytom/errors" "github.com/bytom/bytom/math/checked" @@ -11,13 +13,19 @@ import ( "github.com/bytom/bytom/protocol/state" ) +type applyBlockReply struct { + verification *Verification + isRollback bool + newBestHash bc.Hash +} + // ApplyBlock used to receive a new block from upper layer, it provides idempotence // and parse the vote and mortgage from the transactions, then save to the checkpoint // the tree of checkpoint will grow with the arrival of new blocks // it will return verification when an epoch is reached and the current node is the validator, otherwise return nil // the chain module must broadcast the verification -func (c *Casper) ApplyBlock(block *types.Block) (*Verification, *state.Checkpoint, error) { - if block.Height % state.BlocksOfEpoch == 1 { +func (c *Casper) ApplyBlock(block *types.Block) (*applyBlockReply, error) { + if block.Height%state.BlocksOfEpoch == 1 { c.newEpochCh <- block.PreviousBlockHash } @@ -25,39 +33,44 @@ func (c *Casper) ApplyBlock(block *types.Block) (*Verification, *state.Checkpoin defer c.mu.Unlock() if _, err := c.tree.nodeByHash(block.Hash()); err == nil { - // already processed - return nil, nil, nil + return nil, errAlreadyProcessedBlock } + _, oldBestHash := c.bestChain() target, err := c.applyBlockToCheckpoint(block) if err != nil { - return nil, nil, errors.Wrap(err, "apply block to checkpoint") + return nil, errors.Wrap(err, "apply block to checkpoint") } - if err := c.applyTransactions(target, block.Transactions); err != nil { - return nil, nil, err + if err := applyTransactions(target, block.Transactions); err != nil { + return nil, err } validators, err := c.Validators(&target.Hash) if err != nil { - return nil, nil, err + return nil, err } verification, err := c.applyMyVerification(target, block, validators) if err != nil { - return nil, nil, err + return nil, err } affectedCheckpoints, err := c.applySupLinks(target, block.SupLinks, validators) if err != nil { - return nil, nil, err + return nil, err } - return verification, target, c.saveCheckpoints(affectedCheckpoints, target) + reply := &applyBlockReply{verification: verification} + if c.isRollback(oldBestHash, block) { + reply.isRollback = true + reply.newBestHash = block.Hash() + } + return reply, c.saveCheckpoints(affectedCheckpoints) } func (c *Casper) applyBlockToCheckpoint(block *types.Block) (*state.Checkpoint, error) { - node, err := c.tree.nodeByHash(block.PreviousBlockHash) + node, err := c.checkpointByHash(block.PreviousBlockHash) if err != nil { return nil, err } @@ -79,7 +92,7 @@ func (c *Casper) applyBlockToCheckpoint(block *types.Block) (*state.Checkpoint, for pubKey, num := range parent.Guaranties { checkpoint.Guaranties[pubKey] = num } - node.children = append(node.children, &treeNode{checkpoint: checkpoint}) + node.addChild(&treeNode{checkpoint: checkpoint}) } else if mod == 0 { checkpoint.Status = state.Unjustified } @@ -90,7 +103,68 @@ func (c *Casper) applyBlockToCheckpoint(block *types.Block) (*state.Checkpoint, return checkpoint, nil } -func (c *Casper) applyTransactions(target *state.Checkpoint, transactions []*types.Tx) error { +func (c *Casper) checkpointByHash(blockHash bc.Hash) (*treeNode, error) { + node, err := c.tree.nodeByHash(blockHash) + if err != nil { + logrus.WithField("err", err).Error("fail find checkpoint, start to reorganize checkpoint") + + return c.reorganizeCheckpoint(blockHash) + } + + return node, nil +} + +func (c *Casper) isRollback(oldBestHash bc.Hash, block *types.Block) bool { + _, newBestHash := c.bestChain() + return block.Hash() == newBestHash && block.PreviousBlockHash != oldBestHash +} + +func (c *Casper) reorganizeCheckpoint(hash bc.Hash) (*treeNode, error) { + prevHash := hash + var attachBlocks []*types.Block + for { + prevBlock, err := c.store.GetBlock(&prevHash) + if err != nil { + return nil, err + } + + if prevBlock.Height%state.BlocksOfEpoch == 0 { + break + } + + attachBlocks = append([]*types.Block{prevBlock}, attachBlocks...) + prevHash = prevBlock.PreviousBlockHash + } + + parent, err := c.tree.nodeByHash(prevHash) + if err != nil { + return nil, err + } + + node := &treeNode{ + checkpoint: &state.Checkpoint{ + ParentHash: parent.checkpoint.Hash, + Parent: parent.checkpoint, + Status: state.Growing, + Votes: make(map[string]uint64), + Guaranties: make(map[string]uint64), + }, + } + + parent.addChild(node) + for _, attachBlock := range attachBlocks { + if err := applyTransactions(node.checkpoint, attachBlock.Transactions); err != nil { + return nil, err + } + + node.checkpoint.Hash = attachBlock.Hash() + node.checkpoint.Height = attachBlock.Height + node.checkpoint.Timestamp = attachBlock.Timestamp + } + return node, nil +} + +func applyTransactions(target *state.Checkpoint, transactions []*types.Tx) error { for _, tx := range transactions { for _, input := range tx.Inputs { if vetoInput, ok := input.TypedInput.(*types.VetoInput); ok { @@ -124,12 +198,12 @@ func (c *Casper) applyTransactions(target *state.Checkpoint, transactions []*typ } // applySupLinks copy the block's supLink to the checkpoint -func (c *Casper) applySupLinks(target *state.Checkpoint, supLinks []*types.SupLink, validators map[string]*state.Validator) (map[bc.Hash]*state.Checkpoint, error) { +func (c *Casper) applySupLinks(target *state.Checkpoint, supLinks []*types.SupLink, validators map[string]*state.Validator) ([]*state.Checkpoint, error) { + affectedCheckpoints := []*state.Checkpoint{target} if target.Height%state.BlocksOfEpoch != 0 { return nil, nil } - affectedCheckpoints := make(map[bc.Hash]*state.Checkpoint) for _, supLink := range supLinks { var validVerifications []*Verification for _, v := range supLinkToVerifications(supLink, validators, target.Hash, target.Height) { @@ -143,11 +217,8 @@ func (c *Casper) applySupLinks(target *state.Checkpoint, supLinks []*types.SupLi return nil, err } - for _, c := range checkpoints { - affectedCheckpoints[c.Hash] = c - } + affectedCheckpoints = append(affectedCheckpoints, checkpoints...) } - return affectedCheckpoints, nil } @@ -171,7 +242,7 @@ func (c *Casper) applyMyVerification(target *state.Checkpoint, block *types.Bloc } func (c *Casper) myVerification(target *state.Checkpoint, validators map[string]*state.Validator) (*Verification, error) { - if target.Height % state.BlocksOfEpoch != 0 { + if target.Height%state.BlocksOfEpoch != 0 { return nil, nil } @@ -199,7 +270,7 @@ func (c *Casper) myVerification(target *state.Checkpoint, validators map[string] return nil, err } - if err := c.verifyVerification(v, validatorOrder,false); err != nil { + if err := c.verifyVerification(v, validatorOrder, false); err != nil { return nil, nil } @@ -208,18 +279,6 @@ func (c *Casper) myVerification(target *state.Checkpoint, validators map[string] return nil, nil } -func (c *Casper) saveCheckpoints(affectedCheckpoints map[bc.Hash]*state.Checkpoint, target *state.Checkpoint) error { - // the target checkpoint must eventually be saved in the chain state - delete(affectedCheckpoints, target.Hash) - - var checkpoints []*state.Checkpoint - for _, c := range affectedCheckpoints { - checkpoints = append(checkpoints, c) - } - return c.store.SaveCheckpoints(checkpoints) -} - - type guarantyArgs struct { Amount uint64 PubKey []byte @@ -305,6 +364,20 @@ func (c *Casper) lastJustifiedCheckpointOfBranch(branch *state.Checkpoint) *stat return nil } +func (c *Casper) saveCheckpoints(checkpoints []*state.Checkpoint) error { + checkpointSet := make(map[bc.Hash]*state.Checkpoint) + for _, c := range checkpoints { + checkpointSet[c.Hash] = c + } + + var result []*state.Checkpoint + for _, c := range checkpointSet { + result = append(result, c) + } + + return c.store.SaveCheckpoints(result) +} + func supLinkToVerifications(supLink *types.SupLink, validators map[string]*state.Validator, targetHash bc.Hash, targetHeight uint64) []*Verification { validatorList := make([]*state.Validator, len(validators)) for _, validator := range validators { diff --git a/protocol/auth_verification.go b/protocol/auth_verification.go index a3816464..25d87f69 100644 --- a/protocol/auth_verification.go +++ b/protocol/auth_verification.go @@ -48,7 +48,12 @@ func (c *Casper) AuthVerification(v *Verification) error { return nil } - return c.authVerification(v, targetNode.checkpoint, validators) + _, oldBestHash := c.bestChain() + if err := c.authVerification(v, targetNode.checkpoint, validators); err != nil { + return err + } + + return c.tryRollback(oldBestHash) } func (c *Casper) authVerification(v *Verification, target *state.Checkpoint, validators map[string]*state.Validator) error { @@ -70,17 +75,14 @@ func (c *Casper) authVerification(v *Verification, target *state.Checkpoint, val } func (c *Casper) addVerificationToCheckpoint(target *state.Checkpoint, validators map[string]*state.Validator, verifications ...*Verification) ([]*state.Checkpoint, error) { - _, oldBestHash := c.bestChain() - var affectedCheckpoints []*state.Checkpoint + affectedCheckpoints := []*state.Checkpoint{target} for _, v := range verifications { - source, err := c.store. GetCheckpoint(&v.SourceHash) + source, err := c.store.GetCheckpoint(&v.SourceHash) if err != nil { return nil, err } supLink := target.AddVerification(v.SourceHash, v.SourceHeight, validators[v.PubKey].Order, v.Signature) - affectedCheckpoints = append(affectedCheckpoints, target) - if target.Status != state.Unjustified || !supLink.IsMajority(len(validators)) || source.Status == state.Finalized { continue } @@ -88,12 +90,6 @@ func (c *Casper) addVerificationToCheckpoint(target *state.Checkpoint, validator c.setJustified(source, target) affectedCheckpoints = append(affectedCheckpoints, source) } - - _, newBestHash := c.bestChain() - if oldBestHash != newBestHash { - c.rollbackNotifyCh <- newBestHash - } - return affectedCheckpoints, nil } @@ -134,6 +130,15 @@ func (c *Casper) setFinalized(checkpoint *state.Checkpoint) { c.tree = newRoot } +func (c *Casper) tryRollback(oldBestHash bc.Hash) error { + if _, newBestHash := c.bestChain(); oldBestHash != newBestHash { + msg := &rollbackMsg{bestHash: newBestHash} + c.rollbackCh <- msg + return <-msg.reply + } + return nil +} + func (c *Casper) authVerificationLoop() { for blockHash := range c.newEpochCh { validators, err := c.Validators(&blockHash) diff --git a/protocol/block.go b/protocol/block.go index f2dc01d4..9d9941da 100644 --- a/protocol/block.go +++ b/protocol/block.go @@ -86,13 +86,13 @@ func (c *Chain) connectBlock(block *types.Block) (err error) { return err } - verification, checkpoint, err := c.casper.ApplyBlock(block) + reply, err := c.casper.ApplyBlock(block) if err != nil { return err } - if verification != nil { - if err := c.broadcastVerification(verification); err != nil { + if reply.verification != nil { + if err := c.broadcastVerification(reply.verification); err != nil { return err } } @@ -103,7 +103,7 @@ func (c *Chain) connectBlock(block *types.Block) (err error) { } node := c.index.GetNode(&bcBlock.ID) - if err := c.setState(node, utxoView, contractView, checkpoint); err != nil { + if err := c.setState(node, utxoView, contractView); err != nil { return err } @@ -145,7 +145,6 @@ func (c *Chain) reorganizeChain(node *state.BlockNode) error { } txsToRemove := map[bc.Hash]*types.Tx{} - var affectedCheckpoints []*state.Checkpoint for _, attachNode := range attachNodes { b, err := c.store.GetBlock(&attachNode.Hash) if err != nil { @@ -161,17 +160,6 @@ func (c *Chain) reorganizeChain(node *state.BlockNode) error { return err } - verification, checkpoint, err := c.casper.ApplyBlock(b) - if err != nil { - return err - } - - affectedCheckpoints = append(affectedCheckpoints, checkpoint) - - if err := c.broadcastVerification(verification); err != nil { - return err - } - if err := contractView.ApplyBlock(b); err != nil { return err } @@ -187,7 +175,7 @@ func (c *Chain) reorganizeChain(node *state.BlockNode) error { log.WithFields(log.Fields{"module": logModule, "height": node.Height, "hash": node.Hash.String()}).Debug("attach from mainchain") } - if err := c.setState(node, utxoView, contractView, affectedCheckpoints...); err != nil { + if err := c.setState(node, utxoView, contractView); err != nil { return err } @@ -299,16 +287,20 @@ func (c *Chain) ProcessBlock(block *types.Block) (bool, error) { return response.isOrphan, response.err } +type rollbackMsg struct { + bestHash bc.Hash + reply chan error +} + func (c *Chain) blockProcessor() { for { select { case msg := <-c.processBlockCh: isOrphan, err := c.processBlock(msg.block) msg.reply <- processBlockResponse{isOrphan: isOrphan, err: err} - case newBestHash := <-c.rollbackNotifyCh: - if err := c.rollback(newBestHash); err != nil { - log.WithFields(log.Fields{"module": logModule, "err": err}).Warning("fail on rollback block") - } + case msg := <-c.processRollbackCh: + err := c.rollback(msg.bestHash) + msg.reply <- err } } } @@ -338,15 +330,45 @@ func (c *Chain) processBlock(block *types.Block) (bool, error) { log.WithFields(log.Fields{"module": logModule}).Debug("append block to the end of mainchain") return false, c.connectBlock(bestBlock) } - return false, nil + + log.WithFields(log.Fields{"module": logModule}).Debug("apply fork chain to casper") + return false, c.applyForkChainToCasper(bestNode) +} + +func (c *Chain) applyForkChainToCasper(bestNode *state.BlockNode) error { + attachNodes, _ := c.calcReorganizeNodes(bestNode) + for _, node := range attachNodes { + block, err := c.store.GetBlock(&node.Hash) + if err != nil { + return err + } + + reply, err := c.casper.ApplyBlock(block) + if err != nil { + return err + } + + if reply.verification != nil { + if err := c.broadcastVerification(reply.verification); err != nil { + return err + } + } + + if reply.isRollback { + if err := c.rollback(reply.newBestHash); err != nil { + return err + } + } + } + return nil } -func (c *Chain) rollback(newBestHash bc.Hash) error { - if c.bestNode.Hash == newBestHash { +func (c *Chain) rollback(bestHash bc.Hash) error { + if c.bestNode.Hash == bestHash { return nil } - node := c.index.GetNode(&newBestHash) + node := c.index.GetNode(&bestHash) log.WithFields(log.Fields{"module": logModule}).Debug("start to reorganize chain") return c.reorganizeChain(node) } diff --git a/protocol/casper.go b/protocol/casper.go index 77bec890..52d05f54 100644 --- a/protocol/casper.go +++ b/protocol/casper.go @@ -20,6 +20,7 @@ var ( errVoteToNonValidator = errors.New("pubKey of vote is not validator") errGuarantyLessThanMinimum = errors.New("guaranty less than minimum") errOverflow = errors.New("arithmetic overflow/underflow") + errAlreadyProcessedBlock = errors.New("block already processed in casper") ) const minGuaranty = 1E14 @@ -27,11 +28,11 @@ const minGuaranty = 1E14 // Casper is BFT based proof of stack consensus algorithm, it provides safety and liveness in theory, // it's design mainly refers to https://github.com/ethereum/research/blob/master/papers/casper-basics/casper_basics.pdf type Casper struct { - mu sync.RWMutex - tree *treeNode - rollbackNotifyCh chan bc.Hash - newEpochCh chan bc.Hash - store Store + mu sync.RWMutex + tree *treeNode + rollbackCh chan *rollbackMsg + newEpochCh chan bc.Hash + store Store // pubKey -> conflicting verifications evilValidators map[string][]*Verification // block hash -> previous checkpoint hash @@ -44,52 +45,45 @@ type Casper struct { // argument checkpoints load the checkpoints from leveldb // the first element of checkpoints must genesis checkpoint or the last finalized checkpoint in order to reduce memory space // the others must be successors of first one -func NewCasper(store Store, checkpoints []*state.Checkpoint, rollbackNotifyCh chan bc.Hash) *Casper { +func NewCasper(store Store, checkpoints []*state.Checkpoint, rollbackCh chan *rollbackMsg) *Casper { if checkpoints[0].Height != 0 && checkpoints[0].Status != state.Finalized { log.Panic("first element of checkpoints must genesis or in finalized status") } casper := &Casper{ - tree: makeTree(checkpoints[0], checkpoints[1:]), - rollbackNotifyCh: rollbackNotifyCh, - newEpochCh: make(chan bc.Hash), - store: store, - evilValidators: make(map[string][]*Verification), - prevCheckpointCache: common.NewCache(1024), - verificationCache: common.NewCache(1024), + tree: makeTree(checkpoints[0], checkpoints[1:]), + rollbackCh: rollbackCh, + newEpochCh: make(chan bc.Hash), + store: store, + evilValidators: make(map[string][]*Verification), + prevCheckpointCache: common.NewCache(1024), + verificationCache: common.NewCache(1024), } go casper.authVerificationLoop() return casper } -// Best chain return the chain containing the justified checkpoint of the largest height -func (c *Casper) BestChain() (uint64, bc.Hash) { +// LastFinalized return the block height and block hash which is finalized at last +func (c *Casper) LastFinalized() (uint64, bc.Hash) { c.mu.RLock() defer c.mu.RUnlock() - return c.bestChain() -} - -func (c *Casper) bestChain() (uint64, bc.Hash) { - // root is init justified root := c.tree.checkpoint - bestHeight, bestHash, _ := chainOfMaxJustifiedHeight(c.tree, root.Height) - return bestHeight, bestHash + return root.Height, root.Hash } -// LastFinalized return the block height and block hash which is finalized ast last -func (c *Casper) LastFinalized() (uint64, bc.Hash) { +// LastJustified return the block height and block hash which is justified at last +func (c *Casper) LastJustified() (uint64, bc.Hash) { c.mu.RLock() defer c.mu.RUnlock() - root := c.tree.checkpoint - return root.Height, root.Hash + return lastJustified(c.tree) } // Validators return the validators by specified block hash // e.g. if the block num of epoch is 100, and the block height corresponding to the block hash is 130, then will return the voting results of height in 0~100 func (c *Casper) Validators(blockHash *bc.Hash) (map[string]*state.Validator, error) { - checkpoint, err := c.prevCheckpoint(blockHash) + checkpoint, err := c.parentCheckpoint(blockHash) if err != nil { return nil, err } @@ -97,7 +91,7 @@ func (c *Casper) Validators(blockHash *bc.Hash) (map[string]*state.Validator, er return checkpoint.Validators(), nil } -func (c *Casper) prevCheckpoint(blockHash *bc.Hash) (*state.Checkpoint, error) { +func (c *Casper) parentCheckpoint(blockHash *bc.Hash) (*state.Checkpoint, error) { hash, err := c.prevCheckpointHash(blockHash) if err != nil { return nil, err @@ -106,7 +100,7 @@ func (c *Casper) prevCheckpoint(blockHash *bc.Hash) (*state.Checkpoint, error) { return c.store.GetCheckpoint(hash) } -func (c *Casper) prevCheckpointByPrevHash(prevBlockHash *bc.Hash) (*state.Checkpoint, error) { +func (c *Casper) parentCheckpointByPrevHash(prevBlockHash *bc.Hash) (*state.Checkpoint, error) { hash, err := c.prevCheckpointHashByPrevHash(prevBlockHash) if err != nil { return nil, err @@ -138,6 +132,27 @@ func (c *Casper) EvilValidators() []*EvilValidator { return validators } +func (c *Casper) bestChain() (uint64, bc.Hash) { + // root is init justified + root := c.tree.checkpoint + bestHeight, bestHash, _ := chainOfMaxJustifiedHeight(c.tree, root.Height) + return bestHeight, bestHash +} + +func lastJustified(node *treeNode) (uint64, bc.Hash) { + lastJustifiedHeight, lastJustifiedHash := uint64(0), bc.Hash{} + if node.checkpoint.Status == state.Justified { + lastJustifiedHeight, lastJustifiedHash = node.checkpoint.Height, node.checkpoint.Hash + } + + for _, child := range node.children { + if justifiedHeight, justifiedHash := lastJustified(child); justifiedHeight > lastJustifiedHeight { + lastJustifiedHeight, lastJustifiedHash = justifiedHeight, justifiedHash + } + } + return lastJustifiedHeight, lastJustifiedHash +} + // justifiedHeight is the max justified height of checkpoint from node to root func chainOfMaxJustifiedHeight(node *treeNode, justifiedHeight uint64) (uint64, bc.Hash, uint64) { checkpoint := node.checkpoint @@ -147,7 +162,7 @@ func chainOfMaxJustifiedHeight(node *treeNode, justifiedHeight uint64) (uint64, bestHeight, bestHash, maxJustifiedHeight := checkpoint.Height, checkpoint.Hash, justifiedHeight for _, child := range node.children { - if height, hash, justified := chainOfMaxJustifiedHeight(child, justifiedHeight); justified >= maxJustifiedHeight { + if height, hash, justified := chainOfMaxJustifiedHeight(child, justifiedHeight); justified > maxJustifiedHeight || height > bestHeight { bestHeight, bestHash, maxJustifiedHeight = height, hash, justified } } diff --git a/protocol/protocol.go b/protocol/protocol.go index d9fafe2f..44ca69f0 100644 --- a/protocol/protocol.go +++ b/protocol/protocol.go @@ -15,18 +15,21 @@ import ( "github.com/bytom/bytom/protocol/state" ) -const maxProcessBlockChSize = 1024 +const ( + maxProcessBlockChSize = 1024 + maxProcessRollbackSize = 1024 +) // Chain provides functions for working with the Bytom block chain. type Chain struct { - index *state.BlockIndex - orphanManage *OrphanManage - txPool *TxPool - store Store - casper *Casper - processBlockCh chan *processBlockMsg - rollbackNotifyCh chan bc.Hash - eventDispatcher *event.Dispatcher + index *state.BlockIndex + orphanManage *OrphanManage + txPool *TxPool + store Store + casper *Casper + processBlockCh chan *processBlockMsg + processRollbackCh chan *rollbackMsg + eventDispatcher *event.Dispatcher cond sync.Cond bestNode *state.BlockNode @@ -39,12 +42,12 @@ func NewChain(store Store, txPool *TxPool, eventDispatcher *event.Dispatcher) (* func NewChainWithOrphanManage(store Store, txPool *TxPool, manage *OrphanManage, eventDispatcher *event.Dispatcher) (*Chain, error) { c := &Chain{ - orphanManage: manage, - eventDispatcher: eventDispatcher, - txPool: txPool, - store: store, - rollbackNotifyCh: make(chan bc.Hash), - processBlockCh: make(chan *processBlockMsg, maxProcessBlockChSize), + orphanManage: manage, + eventDispatcher: eventDispatcher, + txPool: txPool, + store: store, + processRollbackCh: make(chan *rollbackMsg, maxProcessRollbackSize), + processBlockCh: make(chan *processBlockMsg, maxProcessBlockChSize), } c.cond.L = new(sync.Mutex) @@ -64,7 +67,7 @@ func NewChainWithOrphanManage(store Store, txPool *TxPool, manage *OrphanManage, c.bestNode = c.index.GetNode(storeStatus.Hash) c.index.SetMainChain(c.bestNode) - casper, err := newCasper(store, storeStatus, c.rollbackNotifyCh) + casper, err := newCasper(store, storeStatus, c.processRollbackCh) if err != nil { return nil, err } @@ -87,6 +90,10 @@ func (c *Chain) initChainStatus() error { Status: state.Justified, } + if err := c.store.SaveCheckpoints([]*state.Checkpoint{checkpoint}); err != nil { + return err + } + utxoView := state.NewUtxoViewpoint() bcBlock := types.MapBlock(genesisBlock) if err := utxoView.ApplyBlock(bcBlock); err != nil { @@ -99,21 +106,21 @@ func (c *Chain) initChainStatus() error { } contractView := state.NewContractViewpoint() - return c.store.SaveChainStatus(node, utxoView, contractView, []*state.Checkpoint{checkpoint}, 0, &checkpoint.Hash) + return c.store.SaveChainStatus(node, utxoView, contractView, 0, &checkpoint.Hash) } -func newCasper(store Store, storeStatus *BlockStoreState, rollbackNotifyCh chan bc.Hash) (*Casper, error) { +func newCasper(store Store, storeStatus *BlockStoreState, rollbackCh chan *rollbackMsg) (*Casper, error) { checkpoints, err := store.CheckpointsFromNode(storeStatus.FinalizedHeight, storeStatus.FinalizedHash) if err != nil { return nil, err } - return NewCasper(store, checkpoints, rollbackNotifyCh), nil + return NewCasper(store, checkpoints, rollbackCh), nil } -// BestBlockHeight returns the last irreversible block header of the blockchain -func (c *Chain) LastIrreversibleHeader() *types.BlockHeader { - _, hash := c.casper.LastFinalized() +// LastFinalizedHeader returns the last finalized block header of the block chain +func (c *Chain) LastJustifiedHeader() *types.BlockHeader { + _, hash := c.casper.LastJustified() node := c.index.GetNode(&hash) return node.BlockHeader() } @@ -152,7 +159,7 @@ func (c *Chain) BestBlockHash() *bc.Hash { // GetValidator return validator by specified blockHash and timestamp func (c *Chain) GetValidator(prevHash *bc.Hash, timeStamp uint64) (*state.Validator, error) { - prevCheckpoint, err := c.casper.prevCheckpointByPrevHash(prevHash) + prevCheckpoint, err := c.casper.parentCheckpointByPrevHash(prevHash) if err != nil { return nil, err } @@ -199,9 +206,9 @@ func (c *Chain) SignBlockHeader(blockHeader *types.BlockHeader) { } // This function must be called with mu lock in above level -func (c *Chain) setState(node *state.BlockNode, view *state.UtxoViewpoint, contractView *state.ContractViewpoint, checkpoints ...*state.Checkpoint) error { +func (c *Chain) setState(node *state.BlockNode, view *state.UtxoViewpoint, contractView *state.ContractViewpoint) error { finalizedHeight, finalizedHash := c.casper.LastFinalized() - if err := c.store.SaveChainStatus(node, view, contractView, checkpoints, finalizedHeight, &finalizedHash); err != nil { + if err := c.store.SaveChainStatus(node, view, contractView, finalizedHeight, &finalizedHash); err != nil { return err } diff --git a/protocol/store.go b/protocol/store.go index bfb9bbda..ce5f4723 100644 --- a/protocol/store.go +++ b/protocol/store.go @@ -26,7 +26,7 @@ type Store interface { LoadBlockIndex(uint64) (*state.BlockIndex, error) SaveBlock(*types.Block) error SaveBlockHeader(*types.BlockHeader) error - SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint, *state.ContractViewpoint, []*state.Checkpoint, uint64, *bc.Hash) error + SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint, *state.ContractViewpoint, uint64, *bc.Hash) error } // BlockStoreState represents the core's db status diff --git a/protocol/tree_node.go b/protocol/tree_node.go index 3d0dd83d..ac0b7e45 100644 --- a/protocol/tree_node.go +++ b/protocol/tree_node.go @@ -34,6 +34,16 @@ func makeTree(root *state.Checkpoint, successors []*state.Checkpoint) *treeNode return rootNode } +func (t *treeNode) addChild(child *treeNode) { + for i, n := range t.children { + if n.checkpoint.Hash == child.checkpoint.Hash { + t.children[i] = child + return + } + } + t.children = append(t.children, child) +} + func (t *treeNode) nodeByHash(blockHash bc.Hash) (*treeNode, error) { if c := t.findOnlyOne(func(c *state.Checkpoint) bool { return c.Hash == blockHash diff --git a/protocol/txpool_test.go b/protocol/txpool_test.go index 3924202e..189697af 100644 --- a/protocol/txpool_test.go +++ b/protocol/txpool_test.go @@ -111,7 +111,7 @@ func (s *mockStore) GetContract(hash [32]byte) ([]byte, error) func (s *mockStore) LoadBlockIndex(uint64) (*state.BlockIndex, error) { return nil, nil } func (s *mockStore) SaveBlock(*types.Block) error { return nil } func (s *mockStore) SaveBlockHeader(*types.BlockHeader) error { return nil } -func (s *mockStore) SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint, *state.ContractViewpoint, []*state.Checkpoint, uint64, *bc.Hash) error { +func (s *mockStore) SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint, *state.ContractViewpoint, uint64, *bc.Hash) error { return nil } @@ -614,7 +614,7 @@ func (s *mockStore1) GetContract(hash [32]byte) ([]byte, error) { retu func (s *mockStore1) LoadBlockIndex(uint64) (*state.BlockIndex, error) { return nil, nil } func (s *mockStore1) SaveBlock(*types.Block) error { return nil } func (s *mockStore1) SaveBlockHeader(*types.BlockHeader) error { return nil } -func (s *mockStore1) SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint, *state.ContractViewpoint, []*state.Checkpoint, uint64, *bc.Hash) error { return nil} +func (s *mockStore1) SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint, *state.ContractViewpoint, uint64, *bc.Hash) error { return nil} func TestProcessTransaction(t *testing.T) { txPool := &TxPool{ diff --git a/test/mock/chain.go b/test/mock/chain.go index 8e41a3a8..692efe15 100644 --- a/test/mock/chain.go +++ b/test/mock/chain.go @@ -27,7 +27,7 @@ func NewChain() *Chain { } } -func (c *Chain) LastIrreversibleHeader() *types.BlockHeader { +func (c *Chain) LastJustifiedHeader() *types.BlockHeader { return nil } diff --git a/test/utxo_view/utxo_view_test.go b/test/utxo_view/utxo_view_test.go index 53db6eca..df7744c8 100644 --- a/test/utxo_view/utxo_view_test.go +++ b/test/utxo_view/utxo_view_test.go @@ -293,7 +293,7 @@ func TestAttachOrDetachBlocks(t *testing.T) { utxoViewpoint0.Entries[k] = v } contractView := state.NewContractViewpoint() - if err := store.SaveChainStatus(node, utxoViewpoint0, contractView, nil, 0, &bc.Hash{}); err != nil { + if err := store.SaveChainStatus(node, utxoViewpoint0, contractView, 0, &bc.Hash{}); err != nil { t.Error(err) } @@ -315,7 +315,7 @@ func TestAttachOrDetachBlocks(t *testing.T) { t.Error(err) } } - if err := store.SaveChainStatus(node, utxoViewpoint, contractView, nil,0, &bc.Hash{}); err != nil { + if err := store.SaveChainStatus(node, utxoViewpoint, contractView,0, &bc.Hash{}); err != nil { t.Error(err) }