@@ -2,27 +2,29 @@ package engine_v2
22
33import (
44 "fmt"
5+ "math/big"
6+ "reflect"
57
68 "github.com/XinFinOrg/XDPoSChain/common"
79 "github.com/XinFinOrg/XDPoSChain/consensus"
810 "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
911 "github.com/XinFinOrg/XDPoSChain/core/types"
12+ "github.com/XinFinOrg/XDPoSChain/crypto"
1013 "github.com/XinFinOrg/XDPoSChain/log"
1114)
1215
1316const (
14- NUM_OF_FORENSICS_PARENTS = 2
17+ NUM_OF_FORENSICS_QC = 3
1518)
1619
1720type ForensicProof struct {
18- QcWithSmallerRound utils.QuorumCert
19- QcWithLargerRound utils.QuorumCert
20- DivergingHash common.Hash
21- HashesTillSmallerRoundQc []common.Hash
22- HashesTillLargerRoundQc []common.Hash
23- AcrossEpochs bool
24- QcWithSmallerRoundAddresses []common.Address
25- QcWithLargerRoundAddresses []common.Address
21+ QcWithSmallerRound utils.QuorumCert
22+ QcWithLargerRound utils.QuorumCert
23+ DivergingHash common.Hash
24+ HashesTillSmallerRoundQc []common.Hash
25+ HashesTillLargerRoundQc []common.Hash
26+ AcrossEpochs bool
27+ Attackers []common.Address
2628}
2729
2830// Forensics instance. Placeholder for future properties to be added
@@ -35,20 +37,15 @@ func NewForensics() *Forensics {
3537 return & Forensics {}
3638}
3739
38- /*
39- Entry point for processing forensics.
40- Triggered once processQC is successfully.
41- Forensics runs in a seperate go routine as its no system critical
42- Link to the flow diagram: https://hashlabs.atlassian.net/wiki/spaces/HASHLABS/pages/97878029/Forensics+Diagram+flow
43- */
44- func (f * Forensics ) ProcessForensics (chain consensus.ChainReader , incomingQC utils.QuorumCert ) {
45- log .Info ("Received a QC in forensics" , "QC" , incomingQC )
40+ func (f * Forensics ) ForensicsMonitoring (chain consensus.ChainReader , headerQcToBeCommitted []types.Header , incomingQC utils.QuorumCert ) error {
41+ f .ProcessForensics (chain , incomingQC )
42+ return f .SetCommittedQCs (headerQcToBeCommitted , incomingQC )
4643}
4744
4845// Set the forensics committed QCs list. The order is from grandparent to current header. i.e it shall follow the QC in its header as follow [hcqc1, hcqc2, hcqc3]
4946func (f * Forensics ) SetCommittedQCs (headers []types.Header , incomingQC utils.QuorumCert ) error {
5047 // highestCommitQCs is an array, assign the parentBlockQc and its child as well as its grandchild QC into this array for forensics purposes.
51- if len (headers ) != NUM_OF_FORENSICS_PARENTS {
48+ if len (headers ) != NUM_OF_FORENSICS_QC - 1 {
5249 log .Error ("[SetCommittedQcs] Received input length not equal to 2" , len (headers ))
5350 return fmt .Errorf ("Received headers length not equal to 2 " )
5451 }
@@ -80,13 +77,197 @@ func (f *Forensics) SetCommittedQCs(headers []types.Header, incomingQC utils.Quo
8077 return nil
8178}
8279
80+ /*
81+ Entry point for processing forensics.
82+ Triggered once processQC is successfully.
83+ Forensics runs in a seperate go routine as its no system critical
84+ Link to the flow diagram: https://hashlabs.atlassian.net/wiki/spaces/HASHLABS/pages/97878029/Forensics+Diagram+flow
85+ */
86+ func (f * Forensics ) ProcessForensics (chain consensus.ChainReader , incomingQC utils.QuorumCert ) error {
87+ log .Debug ("Received a QC in forensics" , "QC" , incomingQC )
88+ // Clone the values to a temporary variable
89+ highestCommittedQCs := f .HighestCommittedQCs
90+ if len (highestCommittedQCs ) != NUM_OF_FORENSICS_QC {
91+ log .Error ("[ProcessForensics] HighestCommittedQCs value not set" , "incomingQcProposedBlockHash" , incomingQC .ProposedBlockInfo .Hash , "incomingQcProposedBlockNumber" , incomingQC .ProposedBlockInfo .Number .Uint64 (), "incomingQcProposedBlockRound" , incomingQC .ProposedBlockInfo .Round )
92+ return fmt .Errorf ("HighestCommittedQCs value not set" )
93+ }
94+ // Find the QC1 and QC2. We only care 2 parents in front of the incomingQC. The returned value contains QC1, QC2 and QC3(the incomingQC)
95+ incomingQuorunCerts , err := f .findAncestorQCs (chain , incomingQC , 2 )
96+ if err != nil {
97+ return err
98+ }
99+ isOnTheChain , err := f .checkQCsOnTheSameChain (chain , highestCommittedQCs , incomingQuorunCerts )
100+ if err != nil {
101+ return err
102+ }
103+ if isOnTheChain {
104+ // Passed the checking, nothing suspecious.
105+ log .Debug ("[ProcessForensics] Passed forensics checking, nothing suspecious need to be reported" , "incomingQcProposedBlockHash" , incomingQC .ProposedBlockInfo .Hash , "incomingQcProposedBlockNumber" , incomingQC .ProposedBlockInfo .Number .Uint64 (), "incomingQcProposedBlockRound" , incomingQC .ProposedBlockInfo .Round )
106+ return nil
107+ }
108+ // Trigger the safety Alarm if failed
109+ // First, find the QC in the two sets that have the same round
110+ foundSameRoundQC , sameRoundHCQC , sameRoundQC := f .findQCsInSameRound (highestCommittedQCs , incomingQuorunCerts )
111+
112+ if foundSameRoundQC {
113+ attackersAddress := f .findCommonSigners (sameRoundHCQC , sameRoundQC )
114+ f .SendForensicProof (attackersAddress , sameRoundHCQC , sameRoundQC )
115+ } else {
116+ // Not found, need a more complex approach to find the two QC
117+ ancestorQC , lowerRoundQCs , _ , err := f .findAncestorQcThroughRound (chain , highestCommittedQCs , incomingQuorunCerts )
118+ if err != nil {
119+ log .Error ("[ProcessForensics] Error while trying to find ancestor QC through round number" , "Error" , err )
120+ }
121+ // Find the common signers within ancestorQC and lowerRoundQCs[NUM_OF_FORENSICS_QC-1]
122+ attackersAddress := f .findCommonSigners (ancestorQC , lowerRoundQCs [NUM_OF_FORENSICS_QC - 1 ])
123+ f .SendForensicProof (attackersAddress , ancestorQC , lowerRoundQCs [NUM_OF_FORENSICS_QC - 1 ])
124+ }
125+
126+ return nil
127+ }
128+
83129// Last step of forensics which sends out detailed proof to report service.
84- func (f * Forensics ) SendForensicProof () {
130+ func (f * Forensics ) SendForensicProof (attackersAddress []common.Address , lqc utils.QuorumCert , hqc utils.QuorumCert ) {
131+
132+ }
133+
134+ // Utils function to help find the n-th previous QC. It returns an array of QC in ascending order including the currentQc as the last item in the array
135+ func (f * Forensics ) findAncestorQCs (chain consensus.ChainReader , currentQc utils.QuorumCert , distanceFromCurrrentQc int ) ([]utils.QuorumCert , error ) {
136+ var quorumCerts []utils.QuorumCert
137+ quorumCertificate := currentQc
138+ // Append the initial value
139+ quorumCerts = append (quorumCerts , quorumCertificate )
140+ // Append the parents
141+ for i := 0 ; i < distanceFromCurrrentQc ; i ++ {
142+ parentHash := quorumCertificate .ProposedBlockInfo .Hash
143+ parentHeader := chain .GetHeaderByHash (parentHash )
144+ if parentHeader == nil {
145+ log .Error ("[findAncestorQCs] Forensics findAncestorQCs unable to find its parent block header" , "BlockNum" , parentHeader .Number .Int64 (), "ParentHash" , parentHash .Hex ())
146+ return nil , fmt .Errorf ("Unable to find parent block header in forensics" )
147+ }
148+ var decodedExtraField utils.ExtraFields_v2
149+ err := utils .DecodeBytesExtraFields (parentHeader .Extra , & decodedExtraField )
150+ if err != nil {
151+ log .Error ("[findAncestorQCs] Error while trying to decode from parent block extra" , "BlockNum" , parentHeader .Number .Int64 (), "ParentHash" , parentHash .Hex ())
152+ }
153+ quorumCertificate = * decodedExtraField .QuorumCert
154+ quorumCerts = append (quorumCerts , quorumCertificate )
155+ }
156+ // The quorumCerts is in the reverse order, we need to flip it
157+ var quorumCertsInAscendingOrder []utils.QuorumCert
158+ for i := len (quorumCerts ) - 1 ; i >= 0 ; i -- {
159+ quorumCertsInAscendingOrder = append (quorumCertsInAscendingOrder , quorumCerts [i ])
160+ }
161+ return quorumCertsInAscendingOrder , nil
162+ }
163+
164+ // Check whether two provided QC set are on the same chain
165+ func (f * Forensics ) checkQCsOnTheSameChain (chain consensus.ChainReader , highestCommittedQCs []utils.QuorumCert , incomingQCandItsParents []utils.QuorumCert ) (bool , error ) {
166+ // Re-order two sets of QCs by block Number
167+ lowerBlockNumQCs := highestCommittedQCs
168+ higherBlockNumQCs := incomingQCandItsParents
169+ if incomingQCandItsParents [0 ].ProposedBlockInfo .Number .Cmp (highestCommittedQCs [0 ].ProposedBlockInfo .Number ) == - 1 {
170+ lowerBlockNumQCs = incomingQCandItsParents
171+ higherBlockNumQCs = highestCommittedQCs
172+ }
173+
174+ proposedBlockInfo := higherBlockNumQCs [0 ].ProposedBlockInfo
175+ for i := 0 ; i < int ((big .NewInt (0 ).Sub (higherBlockNumQCs [0 ].ProposedBlockInfo .Number , lowerBlockNumQCs [0 ].ProposedBlockInfo .Number )).Int64 ()); i ++ {
176+ parentHeader := chain .GetHeaderByHash (proposedBlockInfo .Hash )
177+ var decodedExtraField utils.ExtraFields_v2
178+ err := utils .DecodeBytesExtraFields (parentHeader .Extra , & decodedExtraField )
179+ if err != nil {
180+ log .Error ("[ProcessForensics] Fail to decode extra when checking the two QCs set on the same chain" , "Error" , err )
181+ return false , err
182+ }
183+ proposedBlockInfo = decodedExtraField .QuorumCert .ProposedBlockInfo
184+ }
185+ // Check the final proposed blockInfo is the same as what we have from lowerBlockNumQCs[0]
186+ if reflect .DeepEqual (proposedBlockInfo , lowerBlockNumQCs [0 ].ProposedBlockInfo ) {
187+ return true , nil
188+ }
189+
190+ return false , nil
191+ }
192+
193+ // Given the two QCs set, find if there are any QC that have the same round
194+ func (f * Forensics ) findQCsInSameRound (quorumCerts1 []utils.QuorumCert , quorumCerts2 []utils.QuorumCert ) (bool , utils.QuorumCert , utils.QuorumCert ) {
195+ for _ , quorumCert1 := range quorumCerts1 {
196+ for _ , quorumCert2 := range quorumCerts2 {
197+ if quorumCert1 .ProposedBlockInfo .Round == quorumCert2 .ProposedBlockInfo .Round {
198+ return true , quorumCert1 , quorumCert2
199+ }
200+ }
201+ }
202+ return false , utils.QuorumCert {}, utils.QuorumCert {}
85203}
86204
87- // Find the blockInfo of the block -2 distance away from the QC. Note: We using block number which means not necessary on the same chain as QC received
88- func (f * Forensics ) findParentsQc (chain consensus.ChainReader , currentQc utils.QuorumCert , distanceFromCurrrentQc int64 ) {
205+ // Find the common address that have signed both QCs
206+ func (f * Forensics ) findCommonSigners (quorumCert1 utils.QuorumCert , quorumCert2 utils.QuorumCert ) []common.Address {
207+ var commonSigners []common.Address
208+ quorumCert1SignersMap := make (map [string ]bool )
209+ // The QC signatures are signed by votes special struct VoteForSign
210+ quorumCert1SignedHash := utils .VoteSigHash (& utils.VoteForSign {
211+ ProposedBlockInfo : quorumCert1 .ProposedBlockInfo ,
212+ GapNumber : quorumCert1 .GapNumber ,
213+ })
214+ for _ , signature := range quorumCert1 .Signatures {
215+ var signerAddress common.Address
216+ pubkey , err := crypto .Ecrecover (quorumCert1SignedHash .Bytes (), signature )
217+ if err != nil {
218+ log .Error ("[findCommonSigners] Fail to Ecrecover signer from the quorumCert1SignedHash" , "quorumCert1.GapNumber" , quorumCert1 .GapNumber , "quorumCert1.ProposedBlockInfo" , quorumCert1 .ProposedBlockInfo )
219+ }
220+
221+ copy (signerAddress [:], crypto .Keccak256 (pubkey [1 :])[12 :])
222+ quorumCert1SignersMap [signerAddress .Hex ()] = true
223+ }
224+ // Now, Let's check if quorumCert2 have any signers that have in common with quorumCert1SignersMap(from quorumCert1)
225+ quorumCert2SignedHash := utils .VoteSigHash (& utils.VoteForSign {
226+ ProposedBlockInfo : quorumCert2 .ProposedBlockInfo ,
227+ GapNumber : quorumCert2 .GapNumber ,
228+ })
229+ for _ , signature := range quorumCert2 .Signatures {
230+ var signerAddress common.Address
231+ pubkey , err := crypto .Ecrecover (quorumCert2SignedHash .Bytes (), signature )
232+ if err != nil {
233+ log .Error ("[findCommonSigners] Fail to Ecrecover signer from the quorumCert2SignedHash" , "quorumCert2.GapNumber" , quorumCert2 .GapNumber , "quorumCert2.ProposedBlockInfo" , quorumCert2 .ProposedBlockInfo )
234+ }
235+
236+ copy (signerAddress [:], crypto .Keccak256 (pubkey [1 :])[12 :])
237+ if quorumCert1SignersMap [signerAddress .Hex ()] {
238+ commonSigners = append (commonSigners , signerAddress )
239+ }
240+ }
241+ return commonSigners
89242}
90243
91- func (f * Forensics ) findCommonSigners (currentQc utils.QuorumCert , higherQc utils.QuorumCert ) {
244+ // Check whether the given QCs are on the same chain as the stored committed QCs(f.HighestCommittedQCs) regardless their orders
245+ func (f * Forensics ) findAncestorQcThroughRound (chain consensus.ChainReader , highestCommittedQCs []utils.QuorumCert , incomingQCandItsParents []utils.QuorumCert ) (utils.QuorumCert , []utils.QuorumCert , []utils.QuorumCert , error ) {
246+ /*
247+ Re-order two sets of QCs by Round number
248+ */
249+ lowerRoundQCs := highestCommittedQCs
250+ higherRoundQCs := incomingQCandItsParents
251+ if incomingQCandItsParents [0 ].ProposedBlockInfo .Round < highestCommittedQCs [0 ].ProposedBlockInfo .Round {
252+ lowerRoundQCs = incomingQCandItsParents
253+ higherRoundQCs = highestCommittedQCs
254+ }
255+
256+ // Find the ancestorFromIncomingQC1 that matches round number < lowerRoundQCs3
257+ ancestorQC := higherRoundQCs [0 ]
258+ for ancestorQC .ProposedBlockInfo .Round >= lowerRoundQCs [NUM_OF_FORENSICS_QC - 1 ].ProposedBlockInfo .Round {
259+ proposedBlock := chain .GetHeaderByHash (ancestorQC .ProposedBlockInfo .Hash )
260+ var decodedExtraField utils.ExtraFields_v2
261+ err := utils .DecodeBytesExtraFields (proposedBlock .Extra , & decodedExtraField )
262+ if err != nil {
263+ log .Error ("[findAncestorQcThroughRound] Error while trying to decode extra field" , "ProposedBlockInfo.Hash" , ancestorQC .ProposedBlockInfo .Hash )
264+ return ancestorQC , lowerRoundQCs , higherRoundQCs , err
265+ }
266+ // Found the ancestor QC
267+ if decodedExtraField .QuorumCert .ProposedBlockInfo .Round < lowerRoundQCs [NUM_OF_FORENSICS_QC - 1 ].ProposedBlockInfo .Round {
268+ return ancestorQC , lowerRoundQCs , higherRoundQCs , nil
269+ }
270+ ancestorQC = * decodedExtraField .QuorumCert
271+ }
272+ return ancestorQC , lowerRoundQCs , higherRoundQCs , fmt .Errorf ("[findAncestorQcThroughRound] Could not find ancestor QC" )
92273}
0 commit comments