mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-02-21 10:27:26 +08:00
342 lines
9.6 KiB
Go
342 lines
9.6 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"go.uber.org/zap"
|
|
"source.quilibrium.com/quilibrium/monorepo/consensus"
|
|
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
|
|
"source.quilibrium.com/quilibrium/monorepo/consensus/verification"
|
|
"source.quilibrium.com/quilibrium/monorepo/protobufs"
|
|
"source.quilibrium.com/quilibrium/monorepo/types/tries"
|
|
up2p "source.quilibrium.com/quilibrium/monorepo/utils/p2p"
|
|
)
|
|
|
|
// AppVotingProvider implements VotingProvider
|
|
type AppVotingProvider struct {
|
|
engine *AppConsensusEngine
|
|
}
|
|
|
|
// FinalizeQuorumCertificate implements consensus.VotingProvider.
|
|
func (p *AppVotingProvider) FinalizeQuorumCertificate(
|
|
ctx context.Context,
|
|
state *models.State[*protobufs.AppShardFrame],
|
|
aggregatedSignature models.AggregatedSignature,
|
|
) (models.QuorumCertificate, error) {
|
|
cloned := (*state.State).Clone().(*protobufs.AppShardFrame)
|
|
cloned.Header.PublicKeySignatureBls48581 =
|
|
&protobufs.BLS48581AggregateSignature{
|
|
Signature: aggregatedSignature.GetSignature(),
|
|
PublicKey: &protobufs.BLS48581G2PublicKey{
|
|
KeyValue: aggregatedSignature.GetPubKey(),
|
|
},
|
|
Bitmask: aggregatedSignature.GetBitmask(),
|
|
}
|
|
frameBytes, err := cloned.ToCanonicalBytes()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "finalize quorum certificate")
|
|
}
|
|
|
|
p.engine.pubsub.PublishToBitmask(
|
|
p.engine.getFrameMessageBitmask(),
|
|
frameBytes,
|
|
)
|
|
|
|
return &protobufs.QuorumCertificate{
|
|
Filter: (*state.State).Header.Address,
|
|
Rank: (*state.State).GetRank(),
|
|
FrameNumber: (*state.State).Header.FrameNumber,
|
|
Selector: []byte((*state.State).Identity()),
|
|
Timestamp: uint64(time.Now().UnixMilli()),
|
|
AggregateSignature: &protobufs.BLS48581AggregateSignature{
|
|
Signature: aggregatedSignature.GetSignature(),
|
|
PublicKey: &protobufs.BLS48581G2PublicKey{
|
|
KeyValue: aggregatedSignature.GetPubKey(),
|
|
},
|
|
Bitmask: aggregatedSignature.GetBitmask(),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// FinalizeTimeout implements consensus.VotingProvider.
|
|
func (p *AppVotingProvider) FinalizeTimeout(
|
|
ctx context.Context,
|
|
rank uint64,
|
|
latestQuorumCertificate models.QuorumCertificate,
|
|
latestQuorumCertificateRanks []consensus.TimeoutSignerInfo,
|
|
aggregatedSignature models.AggregatedSignature,
|
|
) (models.TimeoutCertificate, error) {
|
|
ranksInProverOrder := slices.Clone(latestQuorumCertificateRanks)
|
|
provers, err := p.engine.proverRegistry.GetActiveProvers(p.engine.appAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
proverIndexes := map[models.Identity]int{}
|
|
for i, p := range provers {
|
|
proverIndexes[models.Identity(p.Address)] = i
|
|
}
|
|
|
|
sort.Slice(ranksInProverOrder, func(i, j int) bool {
|
|
return proverIndexes[ranksInProverOrder[i].Signer]-
|
|
proverIndexes[ranksInProverOrder[j].Signer] < 0
|
|
})
|
|
|
|
ranks := []uint64{}
|
|
for _, r := range ranksInProverOrder {
|
|
ranks = append(ranks, r.NewestQCRank)
|
|
}
|
|
|
|
return &protobufs.TimeoutCertificate{
|
|
Filter: p.engine.appAddress,
|
|
Rank: rank,
|
|
LatestRanks: ranks,
|
|
LatestQuorumCertificate: latestQuorumCertificate.(*protobufs.QuorumCertificate),
|
|
Timestamp: uint64(time.Now().UnixMilli()),
|
|
AggregateSignature: &protobufs.BLS48581AggregateSignature{
|
|
Signature: aggregatedSignature.GetSignature(),
|
|
PublicKey: &protobufs.BLS48581G2PublicKey{
|
|
KeyValue: aggregatedSignature.GetPubKey(),
|
|
},
|
|
Bitmask: aggregatedSignature.GetBitmask(),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// SignTimeoutVote implements consensus.VotingProvider.
|
|
func (p *AppVotingProvider) SignTimeoutVote(
|
|
ctx context.Context,
|
|
filter []byte,
|
|
currentRank uint64,
|
|
newestQuorumCertificateRank uint64,
|
|
) (**protobufs.ProposalVote, error) {
|
|
// Get signing key
|
|
signer, _, publicKey, _ := p.engine.GetProvingKey(p.engine.config.Engine)
|
|
if publicKey == nil {
|
|
p.engine.logger.Error("no proving key available")
|
|
return nil, errors.Wrap(
|
|
errors.New("no proving key available for voting"),
|
|
"sign vote",
|
|
)
|
|
}
|
|
|
|
// Create vote (signature)
|
|
signatureData := verification.MakeTimeoutMessage(
|
|
filter,
|
|
currentRank,
|
|
newestQuorumCertificateRank,
|
|
)
|
|
|
|
sig, err := signer.SignWithDomain(
|
|
signatureData,
|
|
slices.Concat([]byte("appshardtimeout"), p.engine.appAddress),
|
|
)
|
|
if err != nil {
|
|
p.engine.logger.Error("could not sign vote", zap.Error(err))
|
|
return nil, errors.Wrap(err, "sign vote")
|
|
}
|
|
|
|
voterAddress := p.engine.getAddressFromPublicKey(publicKey)
|
|
|
|
// Create vote message
|
|
vote := &protobufs.ProposalVote{
|
|
Filter: filter, // buildutils:allow-slice-alias slice is static
|
|
FrameNumber: 0,
|
|
Rank: currentRank,
|
|
Selector: nil,
|
|
Timestamp: uint64(time.Now().UnixMilli()),
|
|
PublicKeySignatureBls48581: &protobufs.BLS48581AddressedSignature{
|
|
Address: voterAddress,
|
|
Signature: sig,
|
|
},
|
|
}
|
|
|
|
return &vote, nil
|
|
}
|
|
|
|
// SignVote implements consensus.VotingProvider.
|
|
func (p *AppVotingProvider) SignVote(
|
|
ctx context.Context,
|
|
state *models.State[*protobufs.AppShardFrame],
|
|
) (**protobufs.ProposalVote, error) {
|
|
// Get signing key
|
|
signer, _, publicKey, _ := p.engine.GetProvingKey(p.engine.config.Engine)
|
|
if publicKey == nil {
|
|
p.engine.logger.Error("no proving key available")
|
|
return nil, errors.Wrap(
|
|
errors.New("no proving key available for voting"),
|
|
"sign vote",
|
|
)
|
|
}
|
|
|
|
nextLeader, err := p.engine.LeaderForRank(state.Rank)
|
|
if err != nil {
|
|
p.engine.logger.Error("could not determine next prover", zap.Error(err))
|
|
return nil, errors.Wrap(
|
|
errors.New("could not determine next prover"),
|
|
"sign vote",
|
|
)
|
|
}
|
|
|
|
var extProof []byte
|
|
if nextLeader != p.engine.Self() {
|
|
p.engine.proofCacheMu.RLock()
|
|
proof, ok := p.engine.proofCache[state.Rank]
|
|
p.engine.proofCacheMu.RUnlock()
|
|
|
|
if !ok {
|
|
return nil, errors.Wrap(
|
|
errors.New("no proof ready for vote"),
|
|
"sign vote",
|
|
)
|
|
}
|
|
extProof = proof[:]
|
|
}
|
|
|
|
// Create vote (signature)
|
|
signatureData := verification.MakeVoteMessage(
|
|
(*state.State).Header.Address,
|
|
state.Rank,
|
|
state.Identifier,
|
|
)
|
|
sig, err := signer.SignWithDomain(
|
|
signatureData,
|
|
slices.Concat([]byte("appshard"), p.engine.appAddress),
|
|
)
|
|
if err != nil {
|
|
p.engine.logger.Error("could not sign vote", zap.Error(err))
|
|
return nil, errors.Wrap(err, "sign vote")
|
|
}
|
|
|
|
voterAddress := p.engine.getAddressFromPublicKey(publicKey)
|
|
|
|
// Create vote message
|
|
vote := &protobufs.ProposalVote{
|
|
Filter: (*state.State).Header.Address,
|
|
FrameNumber: (*state.State).Header.FrameNumber,
|
|
Rank: (*state.State).Header.Rank,
|
|
Selector: []byte((*state.State).Identity()),
|
|
Timestamp: uint64(time.Now().UnixMilli()),
|
|
PublicKeySignatureBls48581: &protobufs.BLS48581AddressedSignature{
|
|
Address: voterAddress,
|
|
Signature: slices.Concat(sig, extProof),
|
|
},
|
|
}
|
|
|
|
return &vote, nil
|
|
}
|
|
|
|
// GetFullPath converts a key to its path representation using 6-bit nibbles
|
|
func GetFullPath(key []byte) []int32 {
|
|
var nibbles []int32
|
|
depth := 0
|
|
for {
|
|
n1 := getNextNibble(key, depth)
|
|
if n1 == -1 {
|
|
break
|
|
}
|
|
nibbles = append(nibbles, n1)
|
|
depth += tries.BranchBits
|
|
}
|
|
|
|
return nibbles
|
|
}
|
|
|
|
// getNextNibble returns the next BranchBits bits from the key starting at pos
|
|
func getNextNibble(key []byte, pos int) int32 {
|
|
startByte := pos / 8
|
|
if startByte >= len(key) {
|
|
return -1
|
|
}
|
|
|
|
// Calculate how many bits we need from the current byte
|
|
startBit := pos % 8
|
|
bitsFromCurrentByte := 8 - startBit
|
|
|
|
result := int(key[startByte] & ((1 << bitsFromCurrentByte) - 1))
|
|
|
|
if bitsFromCurrentByte >= tries.BranchBits {
|
|
// We have enough bits in the current byte
|
|
return int32((result >> (bitsFromCurrentByte - tries.BranchBits)) &
|
|
tries.BranchMask)
|
|
}
|
|
|
|
// We need bits from the next byte
|
|
result = result << (tries.BranchBits - bitsFromCurrentByte)
|
|
if startByte+1 < len(key) {
|
|
remainingBits := tries.BranchBits - bitsFromCurrentByte
|
|
nextByte := int(key[startByte+1])
|
|
result |= (nextByte >> (8 - remainingBits))
|
|
}
|
|
|
|
return int32(result & tries.BranchMask)
|
|
}
|
|
|
|
// extractShardAddresses extracts all possible shard addresses from a transaction address
|
|
func (p *AppVotingProvider) extractShardAddresses(txAddress []byte) [][]byte {
|
|
var shardAddresses [][]byte
|
|
|
|
// Get the full path from the transaction address
|
|
path := GetFullPath(txAddress)
|
|
|
|
// The first 43 nibbles (258 bits) represent the base shard address
|
|
// We need to extract all possible shard addresses by considering path segments after the 43rd nibble
|
|
if len(path) <= 43 {
|
|
// If the path is too short, just return the original address truncated to 32 bytes
|
|
if len(txAddress) >= 32 {
|
|
shardAddresses = append(shardAddresses, txAddress[:32])
|
|
}
|
|
return shardAddresses
|
|
}
|
|
|
|
// Convert the first 43 nibbles to bytes (base shard address)
|
|
baseShardAddr := txAddress[:32]
|
|
l1 := up2p.GetBloomFilterIndices(baseShardAddr, 256, 3)
|
|
candidates := map[string]struct{}{}
|
|
|
|
// Now generate all possible shard addresses by extending the path
|
|
// Each additional nibble after the 43rd creates a new shard address
|
|
for i := 43; i < len(path); i++ {
|
|
// Create a new shard address by extending the base with this path segment
|
|
extendedAddr := make([]byte, 32)
|
|
copy(extendedAddr, baseShardAddr)
|
|
|
|
// Add the path segment as a byte
|
|
extendedAddr = append(extendedAddr, byte(path[i]))
|
|
|
|
candidates[string(extendedAddr)] = struct{}{}
|
|
}
|
|
|
|
shards, err := p.engine.shardsStore.GetAppShards(
|
|
slices.Concat(l1, baseShardAddr),
|
|
[]uint32{},
|
|
)
|
|
if err != nil {
|
|
return [][]byte{}
|
|
}
|
|
|
|
for _, shard := range shards {
|
|
if _, ok := candidates[string(
|
|
slices.Concat(shard.L2, uint32ToBytes(shard.Path)),
|
|
)]; ok {
|
|
shardAddresses = append(shardAddresses, shard.L2)
|
|
}
|
|
}
|
|
|
|
return shardAddresses
|
|
}
|
|
|
|
func uint32ToBytes(path []uint32) []byte {
|
|
bytes := []byte{}
|
|
for _, p := range path {
|
|
bytes = append(bytes, byte(p))
|
|
}
|
|
return bytes
|
|
}
|
|
|
|
var _ consensus.VotingProvider[*protobufs.AppShardFrame, *protobufs.ProposalVote, PeerID] = (*AppVotingProvider)(nil)
|