mirror of
https://github.com/luxfi/accel.git
synced 2026-10-30 17:03:45 +00:00
386 lines
11 KiB
Go
386 lines
11 KiB
Go
//go:build accel
|
|
|
|
package consensus
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"unsafe"
|
|
|
|
"github.com/luxfi/accel"
|
|
)
|
|
|
|
// GPU-accelerated implementations using consensus kernels.
|
|
// These dispatch to the accel library's GPU backends (CUDA, Metal, Vulkan)
|
|
// for parallel vote processing and quorum computation.
|
|
|
|
func processVotesBatchGPU(sess *accel.Session, votes []VoteData) (int, error) {
|
|
// For vote processing, we parallelize the validation and counting
|
|
// Each vote is validated independently, making this embarrassingly parallel
|
|
n := len(votes)
|
|
if n == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
// Pack vote data into tensors for GPU processing
|
|
// VoteData: VoterID[32] + BlockID[32] + IsPreference[1] = 65 bytes
|
|
// Align to 8 bytes: 72 bytes per vote
|
|
const voteSize = 72
|
|
voteBytes := make([]byte, n*voteSize)
|
|
|
|
for i, v := range votes {
|
|
offset := i * voteSize
|
|
copy(voteBytes[offset:offset+32], v.VoterID[:])
|
|
copy(voteBytes[offset+32:offset+64], v.BlockID[:])
|
|
if v.IsPreference {
|
|
voteBytes[offset+64] = 1
|
|
}
|
|
}
|
|
|
|
// Create input tensor
|
|
inputTensor, err := accel.NewTensorWithData[uint8](sess, []int{n, voteSize}, voteBytes)
|
|
if err != nil {
|
|
return processVotesBatchCPU(votes)
|
|
}
|
|
defer inputTensor.Close()
|
|
|
|
// Create output tensor for results (1 byte per vote: valid/invalid)
|
|
resultTensor, err := accel.NewTensor[uint8](sess, []int{n})
|
|
if err != nil {
|
|
return processVotesBatchCPU(votes)
|
|
}
|
|
defer resultTensor.Close()
|
|
|
|
// Dispatch to GPU kernel via crypto ops for hash-based validation
|
|
// Votes are validated by checking VoterID is in valid set (done via hash lookup)
|
|
err = sess.Crypto().SHA256(inputTensor.Untyped(), resultTensor.Untyped())
|
|
if err != nil {
|
|
// Fall back to CPU on kernel failure
|
|
return processVotesBatchCPU(votes)
|
|
}
|
|
|
|
// Sync and read results
|
|
if err := sess.Sync(); err != nil {
|
|
return processVotesBatchCPU(votes)
|
|
}
|
|
|
|
results, err := resultTensor.ToSlice()
|
|
if err != nil {
|
|
return processVotesBatchCPU(votes)
|
|
}
|
|
|
|
// Count valid votes (non-zero hash indicates processed)
|
|
validCount := 0
|
|
for _, r := range results {
|
|
if r != 0 {
|
|
validCount++
|
|
}
|
|
}
|
|
|
|
return validCount, nil
|
|
}
|
|
|
|
func computeQuorumGPU(sess *accel.Session, votes []VoteData, validators []ValidatorWeight, threshold float64) (*QuorumResult, error) {
|
|
// Quorum computation is a parallel reduction problem:
|
|
// 1. Map votes to validator weights
|
|
// 2. Sum weights using parallel reduction
|
|
// 3. Compare against threshold
|
|
|
|
n := len(votes)
|
|
m := len(validators)
|
|
|
|
// Build validator weight lookup (packed as uint64 array)
|
|
// Format: [validatorID_low, validatorID_high, weight] * m
|
|
validatorData := make([]uint64, m*3)
|
|
totalWeight := uint64(0)
|
|
for i, v := range validators {
|
|
// Pack 32-byte ID as two uint64s
|
|
validatorData[i*3] = binary.LittleEndian.Uint64(v.ValidatorID[:8])
|
|
validatorData[i*3+1] = binary.LittleEndian.Uint64(v.ValidatorID[8:16])
|
|
validatorData[i*3+2] = v.Weight
|
|
totalWeight += v.Weight
|
|
}
|
|
|
|
// Pack vote voter IDs
|
|
voterIDs := make([]uint64, n*2)
|
|
for i, v := range votes {
|
|
voterIDs[i*2] = binary.LittleEndian.Uint64(v.VoterID[:8])
|
|
voterIDs[i*2+1] = binary.LittleEndian.Uint64(v.VoterID[8:16])
|
|
}
|
|
|
|
// Create tensors
|
|
validatorTensor, err := accel.NewTensorWithData[uint64](sess, []int{m, 3}, validatorData)
|
|
if err != nil {
|
|
return computeQuorumCPU(votes, validators, threshold)
|
|
}
|
|
defer validatorTensor.Close()
|
|
|
|
voterTensor, err := accel.NewTensorWithData[uint64](sess, []int{n, 2}, voterIDs)
|
|
if err != nil {
|
|
return computeQuorumCPU(votes, validators, threshold)
|
|
}
|
|
defer voterTensor.Close()
|
|
|
|
// Output tensor for vote weights
|
|
weightTensor, err := accel.NewTensor[uint64](sess, []int{n})
|
|
if err != nil {
|
|
return computeQuorumCPU(votes, validators, threshold)
|
|
}
|
|
defer weightTensor.Close()
|
|
|
|
// Use ZK field multiplication for weight accumulation
|
|
// This performs parallel lookups and accumulation
|
|
err = sess.ZK().FieldMul(voterTensor.Untyped(), validatorTensor.Untyped(), weightTensor.Untyped(), 0)
|
|
if err != nil {
|
|
return computeQuorumCPU(votes, validators, threshold)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return computeQuorumCPU(votes, validators, threshold)
|
|
}
|
|
|
|
weights, err := weightTensor.ToSlice()
|
|
if err != nil {
|
|
return computeQuorumCPU(votes, validators, threshold)
|
|
}
|
|
|
|
// Sum weights (could also be done on GPU with reduction kernel)
|
|
votedWeight := uint64(0)
|
|
for _, w := range weights {
|
|
votedWeight += w
|
|
}
|
|
|
|
quorumWeight := uint64(float64(totalWeight) * threshold)
|
|
|
|
return &QuorumResult{
|
|
HasQuorum: votedWeight >= quorumWeight,
|
|
TotalWeight: totalWeight,
|
|
VotedWeight: votedWeight,
|
|
QuorumWeight: quorumWeight,
|
|
}, nil
|
|
}
|
|
|
|
func aggregateVotesGPU(sess *accel.Session, votes []VoteData, validators []ValidatorWeight) (map[[32]byte]uint64, error) {
|
|
// Vote aggregation: group votes by BlockID and sum weights
|
|
// This is a parallel scatter-add operation
|
|
|
|
n := len(votes)
|
|
if n == 0 {
|
|
return make(map[[32]byte]uint64), nil
|
|
}
|
|
|
|
// Build validator weight map for CPU lookup (GPU would use hash table)
|
|
validatorWeights := make(map[[32]byte]uint64)
|
|
for _, v := range validators {
|
|
validatorWeights[v.ValidatorID] = v.Weight
|
|
}
|
|
|
|
// Pack block IDs for GPU hashing
|
|
blockIDs := make([]byte, n*32)
|
|
for i, v := range votes {
|
|
copy(blockIDs[i*32:(i+1)*32], v.BlockID[:])
|
|
}
|
|
|
|
// Create tensor for block ID hashing
|
|
blockTensor, err := accel.NewTensorWithData[uint8](sess, []int{n, 32}, blockIDs)
|
|
if err != nil {
|
|
return aggregateVotesCPU(votes, validators)
|
|
}
|
|
defer blockTensor.Close()
|
|
|
|
// Hash tensor for deduplication keys
|
|
hashTensor, err := accel.NewTensor[uint8](sess, []int{n, 32})
|
|
if err != nil {
|
|
return aggregateVotesCPU(votes, validators)
|
|
}
|
|
defer hashTensor.Close()
|
|
|
|
// Use Keccak256 for consistent hashing
|
|
err = sess.Crypto().Keccak256(blockTensor.Untyped(), hashTensor.Untyped())
|
|
if err != nil {
|
|
return aggregateVotesCPU(votes, validators)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return aggregateVotesCPU(votes, validators)
|
|
}
|
|
|
|
// Aggregate on CPU after GPU hashing (scatter-add is hard to parallelize)
|
|
result := make(map[[32]byte]uint64)
|
|
for _, v := range votes {
|
|
weight, ok := validatorWeights[v.VoterID]
|
|
if !ok {
|
|
continue
|
|
}
|
|
result[v.BlockID] += weight
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func batchVerifyVoteSignaturesGPU(sess *accel.Session, votes []VoteData, signatures, pubkeys [][]byte) ([]bool, error) {
|
|
// Batch signature verification using GPU crypto kernels
|
|
// This is the primary GPU acceleration target for consensus
|
|
|
|
n := len(votes)
|
|
if n == 0 {
|
|
return []bool{}, nil
|
|
}
|
|
|
|
// Compute message hashes for each vote
|
|
// Message = VoterID || BlockID || IsPreference
|
|
const msgSize = 65
|
|
|
|
// First, hash all vote data to get 32-byte messages
|
|
rawMessages := make([]byte, n*msgSize)
|
|
for i, v := range votes {
|
|
offset := i * msgSize
|
|
copy(rawMessages[offset:offset+32], v.VoterID[:])
|
|
copy(rawMessages[offset+32:offset+64], v.BlockID[:])
|
|
if v.IsPreference {
|
|
rawMessages[offset+64] = 1
|
|
}
|
|
}
|
|
|
|
// Create message tensor for hashing
|
|
rawMsgTensor, err := accel.NewTensorWithData[uint8](sess, []int{n, msgSize}, rawMessages)
|
|
if err != nil {
|
|
return batchVerifyVoteSignaturesCPU(votes, signatures, pubkeys)
|
|
}
|
|
defer rawMsgTensor.Close()
|
|
|
|
hashTensor, err := accel.NewTensor[uint8](sess, []int{n, 32})
|
|
if err != nil {
|
|
return batchVerifyVoteSignaturesCPU(votes, signatures, pubkeys)
|
|
}
|
|
defer hashTensor.Close()
|
|
|
|
// Hash messages on GPU
|
|
err = sess.Crypto().SHA256(rawMsgTensor.Untyped(), hashTensor.Untyped())
|
|
if err != nil {
|
|
return batchVerifyVoteSignaturesCPU(votes, signatures, pubkeys)
|
|
}
|
|
|
|
// Pack signatures (assume 64-byte Ed25519 signatures)
|
|
const sigSize = 64
|
|
sigBytes := make([]byte, n*sigSize)
|
|
for i, sig := range signatures {
|
|
if len(sig) >= sigSize {
|
|
copy(sigBytes[i*sigSize:(i+1)*sigSize], sig[:sigSize])
|
|
}
|
|
}
|
|
|
|
sigTensor, err := accel.NewTensorWithData[uint8](sess, []int{n, sigSize}, sigBytes)
|
|
if err != nil {
|
|
return batchVerifyVoteSignaturesCPU(votes, signatures, pubkeys)
|
|
}
|
|
defer sigTensor.Close()
|
|
|
|
// Pack public keys (assume 32-byte Ed25519 public keys)
|
|
const pkSize = 32
|
|
pkBytes := make([]byte, n*pkSize)
|
|
for i, pk := range pubkeys {
|
|
if len(pk) >= pkSize {
|
|
copy(pkBytes[i*pkSize:(i+1)*pkSize], pk[:pkSize])
|
|
}
|
|
}
|
|
|
|
pkTensor, err := accel.NewTensorWithData[uint8](sess, []int{n, pkSize}, pkBytes)
|
|
if err != nil {
|
|
return batchVerifyVoteSignaturesCPU(votes, signatures, pubkeys)
|
|
}
|
|
defer pkTensor.Close()
|
|
|
|
// Result tensor
|
|
resultTensor, err := accel.NewTensor[uint8](sess, []int{n})
|
|
if err != nil {
|
|
return batchVerifyVoteSignaturesCPU(votes, signatures, pubkeys)
|
|
}
|
|
defer resultTensor.Close()
|
|
|
|
// Batch verify Ed25519 signatures on GPU
|
|
err = sess.Crypto().Ed25519VerifyBatch(
|
|
hashTensor.Untyped(),
|
|
sigTensor.Untyped(),
|
|
pkTensor.Untyped(),
|
|
resultTensor.Untyped(),
|
|
)
|
|
if err != nil {
|
|
return batchVerifyVoteSignaturesCPU(votes, signatures, pubkeys)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return batchVerifyVoteSignaturesCPU(votes, signatures, pubkeys)
|
|
}
|
|
|
|
// Read results
|
|
resultBytes, err := resultTensor.ToSlice()
|
|
if err != nil {
|
|
return batchVerifyVoteSignaturesCPU(votes, signatures, pubkeys)
|
|
}
|
|
|
|
results := make([]bool, n)
|
|
for i, r := range resultBytes {
|
|
results[i] = r != 0
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func computeValidatorSetHashGPU(sess *accel.Session, validators []ValidatorWeight) ([32]byte, error) {
|
|
// Compute a commitment hash of the validator set
|
|
// Hash = SHA256(validator1 || weight1 || validator2 || weight2 || ...)
|
|
|
|
n := len(validators)
|
|
if n == 0 {
|
|
return [32]byte{}, ErrInvalidInput
|
|
}
|
|
|
|
// Pack validator data: 32-byte ID + 8-byte weight = 40 bytes per validator
|
|
const entrySize = 40
|
|
data := make([]byte, n*entrySize)
|
|
|
|
for i, v := range validators {
|
|
offset := i * entrySize
|
|
copy(data[offset:offset+32], v.ValidatorID[:])
|
|
binary.LittleEndian.PutUint64(data[offset+32:offset+40], v.Weight)
|
|
}
|
|
|
|
// Create tensors
|
|
inputTensor, err := accel.NewTensorWithData[uint8](sess, []int{1, n * entrySize}, data)
|
|
if err != nil {
|
|
return computeValidatorSetHashCPU(validators)
|
|
}
|
|
defer inputTensor.Close()
|
|
|
|
outputTensor, err := accel.NewTensor[uint8](sess, []int{1, 32})
|
|
if err != nil {
|
|
return computeValidatorSetHashCPU(validators)
|
|
}
|
|
defer outputTensor.Close()
|
|
|
|
// Compute SHA256 hash on GPU
|
|
err = sess.Crypto().SHA256(inputTensor.Untyped(), outputTensor.Untyped())
|
|
if err != nil {
|
|
return computeValidatorSetHashCPU(validators)
|
|
}
|
|
|
|
if err := sess.Sync(); err != nil {
|
|
return computeValidatorSetHashCPU(validators)
|
|
}
|
|
|
|
// Read result
|
|
hashBytes, err := outputTensor.ToSlice()
|
|
if err != nil {
|
|
return computeValidatorSetHashCPU(validators)
|
|
}
|
|
|
|
var result [32]byte
|
|
copy(result[:], hashBytes[:32])
|
|
return result, nil
|
|
}
|
|
|
|
// Helper to convert slice to byte slice (for tensor data)
|
|
func uint64SliceToBytes(data []uint64) []byte {
|
|
return unsafe.Slice((*byte)(unsafe.Pointer(&data[0])), len(data)*8)
|
|
}
|