diff --git a/consensus/XDPoS/utils/types.go b/consensus/XDPoS/utils/types.go index 4f55973cfd..2dd2228d36 100644 --- a/consensus/XDPoS/utils/types.go +++ b/consensus/XDPoS/utils/types.go @@ -57,3 +57,50 @@ type PublicApiSnapshot struct { Votes []*clique.Vote `json:"votes"` // List of votes cast in chronological order Tally map[common.Address]clique.Tally `json:"tally"` // Current vote tally to avoid recalculating } + +// Round number type in XDPoS 2.0 +type Round uint64 + +// Vote message in XDPoS 2.0 +type Vote struct { + ProposedBlockInfo BlockInfo + Signature []byte +} + +// Timeout message in XDPoS 2.0 +type Timeout struct { + Round Round + Signature []byte +} + +// BFT Sync Info message in XDPoS 2.0 +type SyncInfo struct { + HighestQuorumCert QuorumCert + HighestTimeoutCert TimeoutCert +} + +// Block Info struct in XDPoS 2.0, used for vote message, etc. +type BlockInfo struct { + Hash common.Hash + Round Round + Number *big.Int +} + +// Quorum Certificate struct in XDPoS 2.0 +type QuorumCert struct { + ProposedBlockInfo BlockInfo + Signatures [][]byte +} + +// Timeout Certificate struct in XDPoS 2.0 +type TimeoutCert struct { + Round Round + Signatures [][]byte +} + +// The parsed extra fields in block header in XDPoS 2.0 (excluding the version byte) +// The version byte (consensus version) is the first byte in header's extra and it's only valid with value >= 2 +type ExtraFields_v2 struct { + Round Round + QuorumCert QuorumCert +} diff --git a/consensus/XDPoS/utils/utils.go b/consensus/XDPoS/utils/utils.go index 6d360539b1..fa756d02ed 100644 --- a/consensus/XDPoS/utils/utils.go +++ b/consensus/XDPoS/utils/utils.go @@ -3,6 +3,7 @@ package utils import ( "bytes" "errors" + "fmt" "reflect" "sort" "strconv" @@ -149,3 +150,55 @@ func SigHash(header *types.Header) (hash common.Hash) { hasher.Sum(hash[:0]) return hash } + +// Encode XDPoS 2.0 extra fields into bytes +func (e *ExtraFields_v2) EncodeToBytes() ([]byte, error) { + bytes, err := rlp.EncodeToBytes(e) + if err != nil { + return nil, err + } + versionByte := []byte{2} + return append(versionByte, bytes...), nil +} + +// Decode extra fields for consensus version >= 2 (XDPoS 2.0 and future versions) +func DecodeBytesExtraFields(b []byte, val interface{}) error { + if len(b) == 0 { + return fmt.Errorf("extra field is 0 length") + } + switch b[0] { + case 1: + return fmt.Errorf("consensus version 1 is not applicable for decoding extra fields") + case 2: + return rlp.DecodeBytes(b[1:], val) + default: + return fmt.Errorf("consensus version %d is not defined", b[0]) + } +} + +func rlpHash(x interface{}) (h common.Hash) { + hw := sha3.NewKeccak256() + rlp.Encode(hw, x) + hw.Sum(h[:0]) + return h +} + +func (m *Vote) Hash() common.Hash { + return rlpHash(m) +} + +func (m *Timeout) Hash() common.Hash { + return rlpHash(m) +} + +func (m *SyncInfo) Hash() common.Hash { + return rlpHash(m) +} + +func VoteSigHash(m *BlockInfo) common.Hash { + return rlpHash(m) +} + +func TimeoutSigHash(m *Round) common.Hash { + return rlpHash(m) +} diff --git a/consensus/XDPoS/utils/utils_test.go b/consensus/XDPoS/utils/utils_test.go index 60f4210f58..6dfc70971c 100644 --- a/consensus/XDPoS/utils/utils_test.go +++ b/consensus/XDPoS/utils/utils_test.go @@ -3,6 +3,7 @@ package utils import ( "fmt" "math/big" + "reflect" "testing" "github.com/XinFinOrg/XDPoSChain/common" @@ -82,3 +83,62 @@ func TestCompareSignersLists(t *testing.T) { t.Error("Failed with list has only one signer") } } + +func toyExtraFields() *ExtraFields_v2 { + round := Round(307) + blockInfo := BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(1)} + signature := []byte{1, 2, 3, 4, 5, 6, 7, 8} + signatures := [][]byte{signature} + quorumCert := QuorumCert{ProposedBlockInfo: blockInfo, Signatures: signatures} + e := &ExtraFields_v2{Round: round, QuorumCert: quorumCert} + return e +} +func TestExtraFieldsEncodeDecode(t *testing.T) { + extraFields := toyExtraFields() + encoded, err := extraFields.EncodeToBytes() + if err != nil { + t.Errorf("Error when encoding extra fields") + } + var decoded ExtraFields_v2 + err = DecodeBytesExtraFields(encoded, &decoded) + if err != nil { + t.Errorf("Error when decoding extra fields") + } + if !reflect.DeepEqual(*extraFields, decoded) { + t.Fatalf("Decoded not equal to original extra field, original: %v; decoded: %v", extraFields, decoded) + } +} + +func TestHashAndSigHash(t *testing.T) { + round := Round(307) + blockInfo1 := BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(1)} + blockInfo2 := BlockInfo{Hash: common.BigToHash(big.NewInt(4095)), Round: round - 1, Number: big.NewInt(1)} + signature1 := []byte{1, 2, 3, 4, 5, 6, 7, 8} + signature2 := []byte{1, 2, 3, 4, 5, 6, 7, 7} + signatures1 := [][]byte{signature1} + quorumCert1 := QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures1} + signatures2 := [][]byte{signature2} + quorumCert2 := QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures2} + vote1 := Vote{ProposedBlockInfo: blockInfo1, Signature: signature1} + vote2 := Vote{ProposedBlockInfo: blockInfo1, Signature: signature2} + if vote1.Hash() == vote2.Hash() { + t.Fatalf("Hash of two votes shouldn't equal") + } + timeout1 := Timeout{Round: 10, Signature: signature1} + timeout2 := Timeout{Round: 10, Signature: signature2} + if timeout1.Hash() == timeout2.Hash() { + t.Fatalf("Hash of two timeouts shouldn't equal") + } + syncInfo1 := SyncInfo{HighestQuorumCert: quorumCert1} + syncInfo2 := SyncInfo{HighestQuorumCert: quorumCert2} + if syncInfo1.Hash() == syncInfo2.Hash() { + t.Fatalf("Hash of two sync info shouldn't equal") + } + if VoteSigHash(&blockInfo1) == VoteSigHash(&blockInfo2) { + t.Fatalf("SigHash of two block info shouldn't equal") + } + round2 := Round(999) + if TimeoutSigHash(&round) == TimeoutSigHash(&round2) { + t.Fatalf("SigHash of two round shouldn't equal") + } +}