aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJimmy Hu <jimmy.hu@dexon.org>2018-12-21 12:03:28 +0800
committerWei-Ning Huang <w@dexon.org>2018-12-21 12:03:28 +0800
commite27667c7563e71adc766fb1155c340cca91f33e0 (patch)
tree1aa53d92d11ebff368bce03d703cf32dc3e0dc19
parent63c34273c8264456bd9849e8f5b987f7da27816e (diff)
downloaddexon-e27667c7563e71adc766fb1155c340cca91f33e0.tar.gz
dexon-e27667c7563e71adc766fb1155c340cca91f33e0.tar.zst
dexon-e27667c7563e71adc766fb1155c340cca91f33e0.zip
core: vm: Add `MPKReady` to governance (#97)
* core/vm: Add DKGMPKReady * param: update GenesisHash * vendor: sync to latest core
-rw-r--r--cmd/gdex/dao_test.go2
-rw-r--r--core/governance.go8
-rw-r--r--core/vm/governance.go166
-rw-r--r--dex/governance.go40
-rw-r--r--params/config.go4
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go5
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/authenticator.go8
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/compaction-chain.go12
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go37
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go102
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/crypto.go24
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go2
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go2
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go13
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go6
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/ticker.go12
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/types/dkg/dkg.go21
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/utils/utils.go61
-rw-r--r--vendor/vendor.json44
19 files changed, 481 insertions, 88 deletions
diff --git a/cmd/gdex/dao_test.go b/cmd/gdex/dao_test.go
index 22f82d4f4..648460b1d 100644
--- a/cmd/gdex/dao_test.go
+++ b/cmd/gdex/dao_test.go
@@ -127,7 +127,7 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc
}
defer db.Close()
- genesisHash := common.HexToHash("0x5fc1fdb2eca492d256600c0d96a2ca7bdfd9412ac8557bcab54e05332260e26b")
+ genesisHash := common.HexToHash("0x48fd77589c2a011fdc764237b9f071577799b7dcc618f25a74fca05dc0b1ad2e")
if genesis != "" {
genesisHash = daoGenesisHash
}
diff --git a/core/governance.go b/core/governance.go
index ea0036a0e..45594fb64 100644
--- a/core/governance.go
+++ b/core/governance.go
@@ -121,6 +121,14 @@ func (g *Governance) DKGMasterPublicKeys(round uint64) []*dkgTypes.MasterPublicK
return headHelper.UniqueDKGMasterPublicKeys(big.NewInt(int64(round)))
}
+func (g *Governance) IsDKGMPKReady(round uint64) bool {
+ headHelper := g.GetHeadHelper()
+ config := g.Configuration(round)
+ threshold := 2*uint64(config.DKGSetSize)/3 + 1
+ count := headHelper.DKGMPKReadysCount(big.NewInt(int64(round))).Uint64()
+ return count >= threshold
+}
+
func (g *Governance) IsDKGFinal(round uint64) bool {
headHelper := g.GetHeadHelper()
config := g.Configuration(round)
diff --git a/core/vm/governance.go b/core/vm/governance.go
index 07f0a5479..597399dd5 100644
--- a/core/vm/governance.go
+++ b/core/vm/governance.go
@@ -241,6 +241,48 @@ const GovernanceABIJSON = `
"inputs": [
{
"name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "dkgMPKReadysCount",
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ },
+ {
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "dkgMPKReadys",
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "name": "",
"type": "address"
},
{
@@ -757,6 +799,24 @@ const GovernanceABIJSON = `
"type": "uint256"
},
{
+ "name": "MPKReady",
+ "type": "bytes"
+ }
+ ],
+ "name": "addDKGMPKReady",
+ "outputs": [],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "Round",
+ "type": "uint256"
+ },
+ {
"name": "Finalize",
"type": "bytes"
}
@@ -917,6 +977,15 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) (ret []by
return nil, errExecutionReverted
}
return g.addDKGMasterPublicKey(args.Round, args.PublicKey)
+ case "addDKGMPKReady":
+ args := struct {
+ Round *big.Int
+ MPKReady []byte
+ }{}
+ if err := method.Inputs.Unpack(&args, arguments); err != nil {
+ return nil, errExecutionReverted
+ }
+ return g.addDKGMPKReady(args.Round, args.MPKReady)
case "addDKGFinalize":
args := struct {
Round *big.Int
@@ -1070,6 +1139,30 @@ func RunGovernanceContract(evm *EVM, input []byte, contract *Contract) (ret []by
return nil, errExecutionReverted
}
return res, nil
+ case "dkgReadys":
+ round, addr := new(big.Int), common.Address{}
+ args := []interface{}{&round, &addr}
+ if err := method.Inputs.Unpack(&args, arguments); err != nil {
+ return nil, errExecutionReverted
+ }
+ ready := g.state.DKGMPKReady(round, addr)
+ res, err := method.Outputs.Pack(ready)
+ if err != nil {
+ return nil, errExecutionReverted
+ }
+ return res, nil
+ case "dkgReadysCount":
+ round := new(big.Int)
+ if err := method.Inputs.Unpack(&round, arguments); err != nil {
+ return nil, errExecutionReverted
+ }
+ count := g.state.DKGMPKReadysCount(round)
+ res, err := method.Outputs.Pack(count)
+ if err != nil {
+ return nil, errExecutionReverted
+ }
+ return res, nil
+
case "dkgFinalizeds":
round, addr := new(big.Int), common.Address{}
args := []interface{}{&round, &addr}
@@ -1228,6 +1321,8 @@ const (
crsLoc
dkgMasterPublicKeysLoc
dkgComplaintsLoc
+ dkgReadyLoc
+ dkgReadysCountLoc
dkgFinalizedLoc
dkgFinalizedsCountLoc
ownerLoc
@@ -1716,6 +1811,33 @@ func (s *GovernanceStateHelper) PushDKGComplaint(round *big.Int, complaint []byt
s.appendTo2DByteArray(big.NewInt(dkgComplaintsLoc), round, complaint)
}
+// mapping(address => bool)[] public dkgReady;
+func (s *GovernanceStateHelper) DKGMPKReady(round *big.Int, addr common.Address) bool {
+ baseLoc := new(big.Int).Add(s.getSlotLoc(big.NewInt(dkgReadyLoc)), round)
+ mapLoc := s.getMapLoc(baseLoc, addr.Bytes())
+ return s.getStateBigInt(mapLoc).Cmp(big.NewInt(0)) != 0
+}
+func (s *GovernanceStateHelper) PutDKGMPKReady(round *big.Int, addr common.Address, ready bool) {
+ baseLoc := new(big.Int).Add(s.getSlotLoc(big.NewInt(dkgReadyLoc)), round)
+ mapLoc := s.getMapLoc(baseLoc, addr.Bytes())
+ res := big.NewInt(0)
+ if ready {
+ res = big.NewInt(1)
+ }
+ s.setStateBigInt(mapLoc, res)
+}
+
+// uint256[] public dkgReadysCount;
+func (s *GovernanceStateHelper) DKGMPKReadysCount(round *big.Int) *big.Int {
+ loc := new(big.Int).Add(s.getSlotLoc(big.NewInt(dkgReadysCountLoc)), round)
+ return s.getStateBigInt(loc)
+}
+func (s *GovernanceStateHelper) IncDKGMPKReadysCount(round *big.Int) {
+ loc := new(big.Int).Add(s.getSlotLoc(big.NewInt(dkgReadysCountLoc)), round)
+ count := s.getStateBigInt(loc)
+ s.setStateBigInt(loc, new(big.Int).Add(count, big.NewInt(1)))
+}
+
// mapping(address => bool)[] public dkgFinalized;
func (s *GovernanceStateHelper) DKGFinalized(round *big.Int, addr common.Address) bool {
baseLoc := new(big.Int).Add(s.getSlotLoc(big.NewInt(dkgFinalizedLoc)), round)
@@ -2087,6 +2209,21 @@ func (g *GovernanceContract) addDKGMasterPublicKey(round *big.Int, mpk []byte) (
return nil, errExecutionReverted
}
+ // MPKReady caller is not allowed to propose mpk.
+ if g.state.DKGMPKReady(round, caller) {
+ return g.penalize()
+ }
+
+ // Calculate 2f
+ threshold := new(big.Int).Mul(
+ big.NewInt(2),
+ new(big.Int).Div(g.state.DKGSetSize(), big.NewInt(3)))
+
+ // If 2f + 1 of DKG set is mpk ready, one can not propose mpk anymore.
+ if g.state.DKGMPKReadysCount(round).Cmp(threshold) > 0 {
+ return nil, errExecutionReverted
+ }
+
var dkgMasterPK dkgTypes.MasterPublicKey
if err := rlp.DecodeBytes(mpk, &dkgMasterPK); err != nil {
return g.penalize()
@@ -2107,6 +2244,35 @@ func (g *GovernanceContract) addDKGMasterPublicKey(round *big.Int, mpk []byte) (
return g.useGas(100000)
}
+func (g *GovernanceContract) addDKGMPKReady(round *big.Int, ready []byte) ([]byte, error) {
+ if round.Cmp(g.state.Round()) != 0 {
+ return g.penalize()
+ }
+
+ caller := g.contract.Caller()
+
+ var dkgReady dkgTypes.MPKReady
+ if err := rlp.DecodeBytes(ready, &dkgReady); err != nil {
+ return g.penalize()
+ }
+
+ // DKGFInalize must belongs to someone in DKG set.
+ if !g.inDKGSet(round, dkgReady.ProposerID) {
+ return g.penalize()
+ }
+
+ verified, _ := core.VerifyDKGMPKReadySignature(&dkgReady)
+ if !verified {
+ return g.penalize()
+ }
+
+ if !g.state.DKGMPKReady(round, caller) {
+ g.state.PutDKGMPKReady(round, caller, true)
+ g.state.IncDKGMPKReadysCount(round)
+ }
+
+ return g.useGas(100000)
+}
func (g *GovernanceContract) addDKGFinalize(round *big.Int, finalize []byte) ([]byte, error) {
if round.Cmp(g.state.Round()) != 0 {
return g.penalize()
diff --git a/dex/governance.go b/dex/governance.go
index aeb6d3fb0..bcc2cca51 100644
--- a/dex/governance.go
+++ b/dex/governance.go
@@ -1,3 +1,20 @@
+// Copyright 2018 The dexon-consensus Authors
+// This file is part of the dexon-consensus library.
+//
+// The dexon-consensus library is free software: you can redistribute it
+// and/or modify it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The dexon-consensus library is distributed in the hope that it will be
+// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+// General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the dexon-consensus library. If not, see
+// <http://www.gnu.org/licenses/>.
+
package dex
import (
@@ -195,6 +212,29 @@ func (d *DexconGovernance) AddDKGMasterPublicKey(round uint64, masterPublicKey *
}
}
+// AddDKGMPKReady adds a DKG mpk ready message.
+func (d *DexconGovernance) AddDKGMPKReady(round uint64, ready *dkgTypes.MPKReady) {
+ method := vm.GovernanceContractName2Method["addDKGMPKReady"]
+
+ encoded, err := rlp.EncodeToBytes(ready)
+ if err != nil {
+ log.Error("failed to RLP encode mpk ready to bytes", "err", err)
+ return
+ }
+
+ res, err := method.Inputs.Pack(big.NewInt(int64(round)), encoded)
+ if err != nil {
+ log.Error("failed to pack addDKGMPKReady input", "err", err)
+ return
+ }
+
+ data := append(method.Id(), res...)
+ err = d.sendGovTx(context.Background(), data)
+ if err != nil {
+ log.Error("failed to send addDKGMPKReady tx", "err", err)
+ }
+}
+
// AddDKGFinalize adds a DKG finalize message.
func (d *DexconGovernance) AddDKGFinalize(round uint64, final *dkgTypes.Finalize) {
method := vm.GovernanceContractName2Method["addDKGFinalize"]
diff --git a/params/config.go b/params/config.go
index 3b12b6101..94b9a34fc 100644
--- a/params/config.go
+++ b/params/config.go
@@ -26,8 +26,8 @@ import (
// Genesis hashes to enforce below configs on.
var (
- MainnetGenesisHash = common.HexToHash("0x5fc1fdb2eca492d256600c0d96a2ca7bdfd9412ac8557bcab54e05332260e26b")
- TestnetGenesisHash = common.HexToHash("0x252c41c125e4a9137a39a20b810ddcd33a8023c407cac863ad2326a521375d0f")
+ MainnetGenesisHash = common.HexToHash("0x48fd77589c2a011fdc764237b9f071577799b7dcc618f25a74fca05dc0b1ad2e")
+ TestnetGenesisHash = common.HexToHash("0x3d5427dc4d3674194d217307ae4db88886a826b6343b6b903efe0b446317ee15")
)
// TODO(jimmy): Add DMoment in the config.
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go
index 4cb47b105..fb6536463 100644
--- a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go
+++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go
@@ -248,9 +248,10 @@ func (mgr *agreementMgr) processAgreementResult(
"hash", result.BlockHash)
mgr.network.PullBlocks(common.Hashes{result.BlockHash})
mgr.logger.Debug("Calling Governance.CRS", "round", result.Position.Round)
- crs := mgr.gov.CRS(result.Position.Round)
+ crs := utils.GetCRSWithPanic(mgr.gov, result.Position.Round, mgr.logger)
nIDs := nodes.GetSubSet(
- int(mgr.gov.Configuration(result.Position.Round).NotarySetSize),
+ int(utils.GetConfigWithPanic(
+ mgr.gov, result.Position.Round, mgr.logger).NotarySetSize),
types.NewNotarySetTarget(crs, result.Position.ChainID))
for key := range result.Votes {
if err := agreement.processVote(&result.Votes[key]); err != nil {
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/authenticator.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/authenticator.go
index 5d176cfee..8e57f719f 100644
--- a/vendor/github.com/dexon-foundation/dexon-consensus/core/authenticator.go
+++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/authenticator.go
@@ -103,6 +103,14 @@ func (au *Authenticator) SignDKGPartialSignature(
return
}
+// SignDKGMPKReady signs a DKG ready message.
+func (au *Authenticator) SignDKGMPKReady(
+ ready *typesDKG.MPKReady) (err error) {
+ ready.ProposerID = au.proposerID
+ ready.Signature, err = au.prvKey.Sign(hashDKGMPKReady(ready))
+ return
+}
+
// SignDKGFinalize signs a DKG finalize message.
func (au *Authenticator) SignDKGFinalize(
final *typesDKG.Finalize) (err error) {
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/compaction-chain.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/compaction-chain.go
index dcd99f497..14e3b265d 100644
--- a/vendor/github.com/dexon-foundation/dexon-consensus/core/compaction-chain.go
+++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/compaction-chain.go
@@ -25,6 +25,7 @@ import (
"github.com/dexon-foundation/dexon-consensus/common"
"github.com/dexon-foundation/dexon-consensus/core/crypto"
"github.com/dexon-foundation/dexon-consensus/core/types"
+ "github.com/dexon-foundation/dexon-consensus/core/utils"
)
// Errors for compaction chain module.
@@ -80,7 +81,7 @@ func (cc *compactionChain) init(initBlock *types.Block) {
// It's the bootstrap case, compactionChain would only deliver blocks until
// tips of all chains are received.
if initBlock.Finalization.Height == 0 {
- cc.chainUnsynced = cc.gov.Configuration(uint64(0)).NumChains
+ cc.chainUnsynced = utils.GetConfigWithPanic(cc.gov, 0, nil).NumChains
}
}
@@ -124,8 +125,6 @@ func (cc *compactionChain) processBlock(block *types.Block) error {
}
func (cc *compactionChain) extractBlocks() []*types.Block {
- prevBlock := cc.lastDeliveredBlock()
-
// Check if we're synced.
if !func() bool {
cc.lock.RLock()
@@ -134,7 +133,7 @@ func (cc *compactionChain) extractBlocks() []*types.Block {
return false
}
// Finalization.Height == 0 is syncing from bootstrap.
- if prevBlock.Finalization.Height == 0 {
+ if cc.prevBlock.Finalization.Height == 0 {
return cc.chainUnsynced == 0
}
return true
@@ -144,7 +143,10 @@ func (cc *compactionChain) extractBlocks() []*types.Block {
deliveringBlocks := make([]*types.Block, 0)
cc.lock.Lock()
defer cc.lock.Unlock()
- var block *types.Block
+ var (
+ block *types.Block
+ prevBlock = cc.prevBlock
+ )
for len(cc.pendingBlocks) > 0 &&
(len(cc.blockRandomness[cc.pendingBlocks[0].Hash]) != 0 ||
cc.pendingBlocks[0].Position.Round == 0) {
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go
index 2b3a859ed..d341cb524 100644
--- a/vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go
+++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go
@@ -105,6 +105,13 @@ func (cc *configurationChain) registerDKG(round uint64, threshold int) {
cc.recv,
round,
threshold)
+ go func() {
+ ticker := newTicker(cc.gov, round, TickerDKG)
+ <-ticker.Tick()
+ cc.dkgLock.Lock()
+ defer cc.dkgLock.Unlock()
+ cc.dkg.proposeMPKReady()
+ }()
}
func (cc *configurationChain) runDKG(round uint64) error {
@@ -126,13 +133,34 @@ func (cc *configurationChain) runDKG(round uint64) error {
cc.logger.Warn("DKG already final", "round", round)
return nil
}
+ cc.logger.Debug("Calling Governance.IsDKGMPKReady", "round", round)
+ for !cc.gov.IsDKGMPKReady(round) {
+ cc.logger.Info("DKG MPKs are not ready yet. Try again later...",
+ "nodeID", cc.ID)
+ cc.dkgLock.Unlock()
+ time.Sleep(500 * time.Millisecond)
+ cc.dkgLock.Lock()
+ }
ticker := newTicker(cc.gov, round, TickerDKG)
cc.dkgLock.Unlock()
<-ticker.Tick()
cc.dkgLock.Lock()
- // Phase 2(T = 0): Exchange DKG secret key share.
+ // Check if this node successfully join the protocol.
cc.logger.Debug("Calling Governance.DKGMasterPublicKeys", "round", round)
- cc.dkg.processMasterPublicKeys(cc.gov.DKGMasterPublicKeys(round))
+ mpks := cc.gov.DKGMasterPublicKeys(round)
+ inProtocol := false
+ for _, mpk := range mpks {
+ if mpk.ProposerID == cc.ID {
+ inProtocol = true
+ break
+ }
+ }
+ if !inProtocol {
+ cc.logger.Warn("Failed to join DKG protocol", "round", round)
+ return nil
+ }
+ // Phase 2(T = 0): Exchange DKG secret key share.
+ cc.dkg.processMasterPublicKeys(mpks)
cc.mpkReady = true
for _, prvShare := range cc.pendingPrvShare {
if err := cc.dkg.processPrivateShare(prvShare); err != nil {
@@ -219,7 +247,7 @@ func (cc *configurationChain) runDKG(round uint64) error {
return nil
}
-func (cc *configurationChain) isDKGReady(round uint64) bool {
+func (cc *configurationChain) isDKGFinal(round uint64) bool {
if !cc.gov.IsDKGFinal(round) {
return false
}
@@ -261,7 +289,8 @@ func (cc *configurationChain) recoverDKGInfo(round uint64) error {
return ErrDKGNotReady
}
- threshold := getDKGThreshold(cc.gov.Configuration(round))
+ threshold := getDKGThreshold(
+ utils.GetConfigWithPanic(cc.gov, round, cc.logger))
// Restore group public key.
gpk, err := NewDKGGroupPublicKey(round,
cc.gov.DKGMasterPublicKeys(round),
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go
index 5beaf546a..e45e4be5a 100644
--- a/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go
+++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go
@@ -329,6 +329,16 @@ func (recv *consensusDKGReceiver) ProposeDKGAntiNackComplaint(
recv.network.BroadcastDKGPrivateShare(prv)
}
+// ProposeDKGMPKReady propose a DKGMPKReady message.
+func (recv *consensusDKGReceiver) ProposeDKGMPKReady(ready *typesDKG.MPKReady) {
+ if err := recv.authModule.SignDKGMPKReady(ready); err != nil {
+ recv.logger.Error("Failed to sign DKG ready", "error", err)
+ return
+ }
+ recv.logger.Debug("Calling Governance.AddDKGMPKReady", "ready", ready)
+ recv.gov.AddDKGMPKReady(ready.Round, ready)
+}
+
// ProposeDKGFinalize propose a DKGFinalize message.
func (recv *consensusDKGReceiver) ProposeDKGFinalize(final *typesDKG.Finalize) {
if err := recv.authModule.SignDKGFinalize(final); err != nil {
@@ -400,12 +410,7 @@ func NewConsensus(
}
// Get configuration for genesis round.
var round uint64
- logger.Debug("Calling Governance.Configuration", "round", round)
- config := gov.Configuration(round)
- if config == nil {
- logger.Error("Unable to get configuration", "round", round)
- return nil
- }
+ config := utils.GetConfigWithPanic(gov, round, logger)
// Init lattice.
lattice := NewLattice(
dMoment, round, config, authModule, app, debugApp, db, logger)
@@ -494,12 +499,17 @@ func NewConsensusFromSyncer(
db,
logger)
recv.cfgModule = cfgModule
+ // Check if the application implement Debug interface.
+ var debugApp Debug
+ if a, ok := app.(Debug); ok {
+ debugApp = a
+ }
// Setup Consensus instance.
con := &Consensus{
ID: ID,
ccModule: newCompactionChain(gov),
lattice: latticeModule,
- app: app,
+ app: newNonBlocking(app, debugApp),
gov: gov,
db: db,
network: networkModule,
@@ -545,29 +555,19 @@ func (con *Consensus) prepare(initBlock *types.Block) error {
// full node. We don't have to notify it.
con.roundToNotify = initBlock.Position.Round + 1
initRound := initBlock.Position.Round
- con.logger.Debug("Calling Governance.Configuration", "round", initRound)
- initConfig := con.gov.Configuration(initRound)
+ initConfig := utils.GetConfigWithPanic(con.gov, initRound, con.logger)
// Setup context.
con.ccModule.init(initBlock)
- // Setup agreementMgr module.
- con.logger.Debug("Calling Governance.Configuration", "round", initRound)
- initCfg := con.gov.Configuration(initRound)
- if initCfg == nil {
- return ErrConfigurationNotReady
- }
con.logger.Debug("Calling Governance.CRS", "round", initRound)
initCRS := con.gov.CRS(initRound)
if (initCRS == common.Hash{}) {
return ErrCRSNotReady
}
- if err := con.baMgr.appendConfig(initRound, initCfg, initCRS); err != nil {
+ if err := con.baMgr.appendConfig(initRound, initConfig, initCRS); err != nil {
return err
}
// Setup lattice module.
- initPlusOneCfg := con.gov.Configuration(initRound + 1)
- if initPlusOneCfg == nil {
- return ErrConfigurationNotReady
- }
+ initPlusOneCfg := utils.GetConfigWithPanic(con.gov, initRound+1, con.logger)
if err := con.lattice.AppendConfig(initRound+1, initPlusOneCfg); err != nil {
return err
}
@@ -576,6 +576,7 @@ func (con *Consensus) prepare(initBlock *types.Block) error {
if err != nil {
return err
}
+ // TODO(jimmy): registerDKG should be called after dmoment.
if _, exist := dkgSet[con.ID]; exist {
con.logger.Info("Selected as DKG set", "round", initRound)
con.cfgModule.registerDKG(initRound, getDKGThreshold(initConfig))
@@ -641,7 +642,7 @@ func (con *Consensus) runCRS(round uint64) {
}
con.logger.Debug("Calling Governance.IsDKGFinal to check if ready to run CRS",
"round", round)
- if con.cfgModule.isDKGReady(round) {
+ if con.cfgModule.isDKGFinal(round) {
break
}
con.logger.Debug("DKG is not ready for running CRS. Retry later...",
@@ -650,7 +651,8 @@ func (con *Consensus) runCRS(round uint64) {
}
// Start running next round CRS.
con.logger.Debug("Calling Governance.CRS", "round", round)
- psig, err := con.cfgModule.preparePartialSignature(round, con.gov.CRS(round))
+ psig, err := con.cfgModule.preparePartialSignature(
+ round, utils.GetCRSWithPanic(con.gov, round, con.logger))
if err != nil {
con.logger.Error("Failed to prepare partial signature", "error", err)
} else if err = con.authModule.SignDKGPartialSignature(psig); err != nil {
@@ -664,7 +666,8 @@ func (con *Consensus) runCRS(round uint64) {
"hash", psig.Hash)
con.network.BroadcastDKGPartialSignature(psig)
con.logger.Debug("Calling Governance.CRS", "round", round)
- crs, err := con.cfgModule.runCRSTSig(round, con.gov.CRS(round))
+ crs, err := con.cfgModule.runCRSTSig(
+ round, utils.GetCRSWithPanic(con.gov, round, con.logger))
if err != nil {
con.logger.Error("Failed to run CRS Tsig", "error", err)
} else {
@@ -709,12 +712,11 @@ func (con *Consensus) initialRound(
time.Sleep(500 * time.Millisecond)
}
// Notify BA for new round.
- con.logger.Debug("Calling Governance.Configuration",
- "round", nextRound)
- nextConfig := con.gov.Configuration(nextRound)
+ nextConfig := utils.GetConfigWithPanic(
+ con.gov, nextRound, con.logger)
con.logger.Debug("Calling Governance.CRS",
"round", nextRound)
- nextCRS := con.gov.CRS(nextRound)
+ nextCRS := utils.GetCRSWithPanic(con.gov, nextRound, con.logger)
if err := con.baMgr.appendConfig(
nextRound, nextConfig, nextCRS); err != nil {
panic(err)
@@ -753,9 +755,8 @@ func (con *Consensus) initialRound(
defer con.dkgReady.L.Unlock()
con.dkgRunning = 0
}()
- con.logger.Debug("Calling Governance.Configuration",
- "round", nextRound)
- nextConfig := con.gov.Configuration(nextRound)
+ nextConfig := utils.GetConfigWithPanic(
+ con.gov, nextRound, con.logger)
con.runDKG(nextRound, nextConfig)
})
}(round + 1)
@@ -766,9 +767,7 @@ func (con *Consensus) initialRound(
// Change round.
// Get configuration for next round.
nextRound := round + 1
- con.logger.Debug("Calling Governance.Configuration",
- "round", nextRound)
- nextConfig := con.gov.Configuration(nextRound)
+ nextConfig := utils.GetConfigWithPanic(con.gov, nextRound, con.logger)
con.initialRound(
startTime.Add(config.RoundInterval), nextRound, nextConfig)
})
@@ -990,7 +989,7 @@ func (con *Consensus) ProcessBlockRandomnessResult(
"randomness", hex.EncodeToString(rand.Randomness))
con.network.BroadcastRandomnessResult(rand)
}
- return nil
+ return con.deliverFinalizedBlocks()
}
// preProcessBlock performs Byzantine Agreement on the block.
@@ -1021,9 +1020,7 @@ func (con *Consensus) deliverBlock(b *types.Block) {
// - roundShift
// - notifyGenesisRound
futureRound := con.roundToNotify + 1
- con.logger.Debug("Calling Governance.Configuration",
- "round", con.roundToNotify)
- futureConfig := con.gov.Configuration(futureRound)
+ futureConfig := utils.GetConfigWithPanic(con.gov, futureRound, con.logger)
con.logger.Debug("Append Config", "round", futureRound)
if err := con.lattice.AppendConfig(
futureRound, futureConfig); err != nil {
@@ -1046,6 +1043,28 @@ func (con *Consensus) deliverBlock(b *types.Block) {
}
}
+// deliverFinalizedBlocks extracts and delivers finalized blocks to application
+// layer.
+func (con *Consensus) deliverFinalizedBlocks() error {
+ con.lock.Lock()
+ defer con.lock.Unlock()
+ return con.deliverFinalizedBlocksWithoutLock()
+}
+
+func (con *Consensus) deliverFinalizedBlocksWithoutLock() (err error) {
+ deliveredBlocks := con.ccModule.extractBlocks()
+ con.logger.Debug("Last blocks in compaction chain",
+ "delivered", con.ccModule.lastDeliveredBlock(),
+ "pending", con.ccModule.lastPendingBlock())
+ for _, b := range deliveredBlocks {
+ con.deliverBlock(b)
+ }
+ if err = con.lattice.PurgeBlocks(deliveredBlocks); err != nil {
+ return
+ }
+ return
+}
+
// processBlock is the entry point to submit one block to a Consensus instance.
func (con *Consensus) processBlock(block *types.Block) (err error) {
if err = con.db.PutBlock(*block); err != nil && err != db.ErrBlockExists {
@@ -1079,14 +1098,7 @@ func (con *Consensus) processBlock(block *types.Block) (err error) {
}
go con.event.NotifyTime(b.Finalization.Timestamp)
}
- deliveredBlocks = con.ccModule.extractBlocks()
- con.logger.Debug("Last blocks in compaction chain",
- "delivered", con.ccModule.lastDeliveredBlock(),
- "pending", con.ccModule.lastPendingBlock())
- for _, b := range deliveredBlocks {
- con.deliverBlock(b)
- }
- if err = con.lattice.PurgeBlocks(deliveredBlocks); err != nil {
+ if err = con.deliverFinalizedBlocksWithoutLock(); err != nil {
return
}
return
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto.go
index 914ca0865..d4a7f0ead 100644
--- a/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto.go
+++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto.go
@@ -243,6 +243,30 @@ func verifyDKGPartialSignatureSignature(
return true, nil
}
+func hashDKGMPKReady(ready *typesDKG.MPKReady) common.Hash {
+ binaryRound := make([]byte, 8)
+ binary.LittleEndian.PutUint64(binaryRound, ready.Round)
+
+ return crypto.Keccak256Hash(
+ ready.ProposerID.Hash[:],
+ binaryRound,
+ )
+}
+
+// VerifyDKGMPKReadySignature verifies DKGMPKReady signature.
+func VerifyDKGMPKReadySignature(
+ ready *typesDKG.MPKReady) (bool, error) {
+ hash := hashDKGMPKReady(ready)
+ pubKey, err := crypto.SigToPub(hash, ready.Signature)
+ if err != nil {
+ return false, err
+ }
+ if ready.ProposerID != types.NewNodeID(pubKey) {
+ return false, nil
+ }
+ return true, nil
+}
+
func hashDKGFinalize(final *typesDKG.Finalize) common.Hash {
binaryRound := make([]byte, 8)
binary.LittleEndian.PutUint64(binaryRound, final.Round)
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go
index 3b5994b26..75c30372f 100644
--- a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go
+++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go
@@ -163,7 +163,7 @@ func (lvl *LevelDBBackedDB) PutCompactionChainTipInfo(
if err != nil {
return err
}
- if info.Height >= height {
+ if info.Height+1 != height {
return ErrInvalidCompactionChainTipHeight
}
if err = lvl.db.Put(compactionChainTipInfoKey, marshaled, nil); err != nil {
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go
index 7393de9db..4bc08e704 100644
--- a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go
+++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go
@@ -149,7 +149,7 @@ func (m *MemBackedDB) PutCompactionChainTipInfo(
blockHash common.Hash, height uint64) error {
m.compactionChainTipLock.Lock()
defer m.compactionChainTipLock.Unlock()
- if m.compactionChainTipHeight >= height {
+ if m.compactionChainTipHeight+1 != height {
return ErrInvalidCompactionChainTipHeight
}
m.compactionChainTipHeight = height
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go
index 8e03cbbda..ef12cf992 100644
--- a/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go
+++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go
@@ -26,6 +26,7 @@ import (
"github.com/dexon-foundation/dexon-consensus/core/crypto/dkg"
"github.com/dexon-foundation/dexon-consensus/core/types"
typesDKG "github.com/dexon-foundation/dexon-consensus/core/types/dkg"
+ "github.com/dexon-foundation/dexon-consensus/core/utils"
)
// Errors for dkg module.
@@ -67,6 +68,9 @@ type dkgReceiver interface {
// ProposeDKGAntiNackComplaint propose a DKGPrivateShare as an anti complaint.
ProposeDKGAntiNackComplaint(prv *typesDKG.PrivateShare)
+ // ProposeDKGMPKReady propose a DKGMPKReady message.
+ ProposeDKGMPKReady(ready *typesDKG.MPKReady)
+
// ProposeDKGFinalize propose a DKGFinalize message.
ProposeDKGFinalize(final *typesDKG.Finalize)
}
@@ -338,6 +342,13 @@ func (d *dkgProtocol) processPrivateShare(
return nil
}
+func (d *dkgProtocol) proposeMPKReady() {
+ d.recv.ProposeDKGMPKReady(&typesDKG.MPKReady{
+ ProposerID: d.ID,
+ Round: d.round,
+ })
+}
+
func (d *dkgProtocol) proposeFinalize() {
d.recv.ProposeDKGFinalize(&typesDKG.Finalize{
ProposerID: d.ID,
@@ -491,7 +502,7 @@ func (tc *TSigVerifierCache) Update(round uint64) (bool, error) {
gpk, err := NewDKGGroupPublicKey(round,
tc.intf.DKGMasterPublicKeys(round),
tc.intf.DKGComplaints(round),
- int(tc.intf.Configuration(round).DKGSetSize/3)+1)
+ int(utils.GetConfigWithPanic(tc.intf, round, nil).DKGSetSize/3)+1)
if err != nil {
return false, err
}
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go
index 2ebfe8621..fc3bf09bc 100644
--- a/vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go
+++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go
@@ -130,6 +130,12 @@ type Governance interface {
// DKGMasterPublicKeys gets all the DKGMasterPublicKey of round.
DKGMasterPublicKeys(round uint64) []*typesDKG.MasterPublicKey
+ // AddDKGMPKReady adds a DKG ready message.
+ AddDKGMPKReady(round uint64, ready *typesDKG.MPKReady)
+
+ // IsDKGFinal checks if DKG is ready.
+ IsDKGMPKReady(round uint64) bool
+
// AddDKGFinalize adds a DKG finalize message.
AddDKGFinalize(round uint64, final *typesDKG.Finalize)
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/ticker.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/ticker.go
index 3728a79e6..f8d0c67d9 100644
--- a/vendor/github.com/dexon-foundation/dexon-consensus/core/ticker.go
+++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/ticker.go
@@ -17,7 +17,11 @@
package core
-import "time"
+import (
+ "time"
+
+ "github.com/dexon-foundation/dexon-consensus/core/utils"
+)
// TickerType is the type of ticker.
type TickerType int
@@ -65,11 +69,11 @@ func newTicker(gov Governance, round uint64, tickerType TickerType) (t Ticker) {
var duration time.Duration
switch tickerType {
case TickerBA:
- duration = gov.Configuration(round).LambdaBA
+ duration = utils.GetConfigWithPanic(gov, round, nil).LambdaBA
case TickerDKG:
- duration = gov.Configuration(round).LambdaDKG
+ duration = utils.GetConfigWithPanic(gov, round, nil).LambdaDKG
case TickerCRS:
- duration = gov.Configuration(round).RoundInterval / 2
+ duration = utils.GetConfigWithPanic(gov, round, nil).RoundInterval / 2
}
t = newDefaultTicker(duration)
}
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/types/dkg/dkg.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/types/dkg/dkg.go
index cecc4f16c..f021d1bfb 100644
--- a/vendor/github.com/dexon-foundation/dexon-consensus/core/types/dkg/dkg.go
+++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/types/dkg/dkg.go
@@ -167,6 +167,27 @@ type PartialSignature struct {
Signature crypto.Signature `json:"signature"`
}
+// MPKReady describe a dig ready message in DKG protocol.
+type MPKReady struct {
+ ProposerID types.NodeID `json:"proposer_id"`
+ Round uint64 `json:"round"`
+ Signature crypto.Signature `json:"signature"`
+}
+
+func (ready *MPKReady) String() string {
+ return fmt.Sprintf("DKGMPKReady{RP:%s Round:%d}",
+ ready.ProposerID.String()[:6],
+ ready.Round)
+}
+
+// Equal check equality of two MPKReady instances.
+func (ready *MPKReady) Equal(other *MPKReady) bool {
+ return ready.ProposerID.Equal(other.ProposerID) &&
+ ready.Round == other.Round &&
+ ready.Signature.Type == other.Signature.Type &&
+ bytes.Compare(ready.Signature.Signature, other.Signature.Signature) == 0
+}
+
// Finalize describe a dig finalize message in DKG protocol.
type Finalize struct {
ProposerID types.NodeID `json:"proposer_id"`
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/utils.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/utils.go
new file mode 100644
index 000000000..3e3803d06
--- /dev/null
+++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/utils.go
@@ -0,0 +1,61 @@
+// This file is part of the dexon-consensus library.
+//
+// The dexon-consensus library is free software: you can redistribute it
+// and/or modify it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The dexon-consensus library is distributed in the hope that it will be
+// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+// General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the dexon-consensus library. If not, see
+// <http://www.gnu.org/licenses/>.
+
+package utils
+
+import (
+ "fmt"
+
+ "github.com/dexon-foundation/dexon-consensus/common"
+ "github.com/dexon-foundation/dexon-consensus/core/types"
+)
+
+type configAccessor interface {
+ Configuration(round uint64) *types.Config
+}
+
+// GetConfigWithPanic is a helper to access configs, and panic when config for
+// that round is not ready yet.
+func GetConfigWithPanic(accessor configAccessor, round uint64,
+ logger common.Logger) *types.Config {
+ if logger != nil {
+ logger.Debug("Calling Governance.Configuration", "round", round)
+ }
+ c := accessor.Configuration(round)
+ if c == nil {
+ panic(fmt.Errorf("configuration is not ready %v", round))
+ }
+ return c
+}
+
+type crsAccessor interface {
+ CRS(round uint64) common.Hash
+}
+
+// GetCRSWithPanic is a helper to access CRS, and panic when CRS for that
+// round is not ready yet.
+func GetCRSWithPanic(accessor crsAccessor, round uint64,
+ logger common.Logger) common.Hash {
+ if logger != nil {
+ logger.Debug("Calling Governance.CRS", "round", round)
+ }
+ crs := accessor.CRS(round)
+ if (crs == common.Hash{}) {
+ panic(fmt.Errorf("CRS is not ready %v", round))
+ }
+ return crs
+
+}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index afe5839ee..480fad67c 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -105,14 +105,14 @@
{
"checksumSHA1": "65L1yf+f0OCiLFniljqfRxVdsQA=",
"path": "github.com/dexon-foundation/dexon-consensus/common",
- "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46",
- "revisionTime": "2018-12-16T06:44:15Z"
+ "revision": "146ed32cf841151b826eafd7d6ade188c56865bf",
+ "revisionTime": "2018-12-20T09:26:30Z"
},
{
- "checksumSHA1": "jZLh+ZsuMIiMuksK9c/5QpMQ2IM=",
+ "checksumSHA1": "DxtHhW/eYFodiPxgwu7opvIhJu0=",
"path": "github.com/dexon-foundation/dexon-consensus/core",
- "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46",
- "revisionTime": "2018-12-16T06:44:15Z"
+ "revision": "146ed32cf841151b826eafd7d6ade188c56865bf",
+ "revisionTime": "2018-12-20T09:26:30Z"
},
{
"checksumSHA1": "v4fKR7uhoyufi6hAVO44cFEb+tY=",
@@ -123,44 +123,44 @@
{
"checksumSHA1": "tQSbYCu5P00lUhKsx3IbBZCuSLY=",
"path": "github.com/dexon-foundation/dexon-consensus/core/crypto",
- "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46",
- "revisionTime": "2018-12-16T06:44:15Z"
+ "revision": "146ed32cf841151b826eafd7d6ade188c56865bf",
+ "revisionTime": "2018-12-20T09:26:30Z"
},
{
"checksumSHA1": "W2P7pkuJ+26BpJg03K4Y0nB5obI=",
"path": "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg",
- "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46",
- "revisionTime": "2018-12-16T06:44:15Z"
+ "revision": "146ed32cf841151b826eafd7d6ade188c56865bf",
+ "revisionTime": "2018-12-20T09:26:30Z"
},
{
"checksumSHA1": "6Pf6caC8LTNCI7IflFmglKYnxYo=",
"path": "github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa",
- "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46",
- "revisionTime": "2018-12-16T06:44:15Z"
+ "revision": "146ed32cf841151b826eafd7d6ade188c56865bf",
+ "revisionTime": "2018-12-20T09:26:30Z"
},
{
- "checksumSHA1": "trkFVPLd7UFFUzL8bn6KuvFU9gE=",
+ "checksumSHA1": "PJXR1OuWwVVYrdJMK3skPr1/8ls=",
"path": "github.com/dexon-foundation/dexon-consensus/core/db",
- "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46",
- "revisionTime": "2018-12-16T06:44:15Z"
+ "revision": "146ed32cf841151b826eafd7d6ade188c56865bf",
+ "revisionTime": "2018-12-20T09:26:30Z"
},
{
"checksumSHA1": "Z079qQV+aQV9A3kSJ0LbFjx5VO4=",
"path": "github.com/dexon-foundation/dexon-consensus/core/types",
- "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46",
- "revisionTime": "2018-12-16T06:44:15Z"
+ "revision": "146ed32cf841151b826eafd7d6ade188c56865bf",
+ "revisionTime": "2018-12-20T09:26:30Z"
},
{
- "checksumSHA1": "Sn3PAYsblIXmr7gVKDzxnoBPku4=",
+ "checksumSHA1": "sY+2eiOoWvsNMvuPl9qQ+rlT9sA=",
"path": "github.com/dexon-foundation/dexon-consensus/core/types/dkg",
- "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46",
- "revisionTime": "2018-12-16T06:44:15Z"
+ "revision": "146ed32cf841151b826eafd7d6ade188c56865bf",
+ "revisionTime": "2018-12-20T09:26:30Z"
},
{
- "checksumSHA1": "A7UQ+7rv9FuElmFBEn/ZdhBqFKI=",
+ "checksumSHA1": "WEtKiyBYr0oPUSF+smv1A2LPBuI=",
"path": "github.com/dexon-foundation/dexon-consensus/core/utils",
- "revision": "99d72382687196fb15ea6ab0fcf297b9ab10ac46",
- "revisionTime": "2018-12-16T06:44:15Z"
+ "revision": "146ed32cf841151b826eafd7d6ade188c56865bf",
+ "revisionTime": "2018-12-20T09:26:30Z"
},
{
"checksumSHA1": "TAkwduKZqLyimyTPPWIllZWYFuE=",