mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-22 03:30:35 +00:00
acc307b959
* add max/min span visualisation tool cli * go mod tidy * lint imports * remove typo * fix epoch table value * fix deepsource * add dep to bazel * fix dep import order * change command name from span to slasher-span-display * change command args style using - instead of _ * sed s/CONFIGURATION/SLASHER PARAMS// * change double neg to double pos condition * remove unused anonymous func * better function naming * add range condition * [deepsource] Fix Empty slice literal used to declare a variable GO-W1027 * correct typo * do not show incorrect epochs due to round robin * fix import --------- Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>
178 lines
6.8 KiB
Go
178 lines
6.8 KiB
Go
package slasher
|
|
|
|
import (
|
|
ssz "github.com/prysmaticlabs/fastssz"
|
|
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
|
)
|
|
|
|
// Parameters for slashing detection.
|
|
//
|
|
// To properly access the element at epoch `e` for a validator index `i`, we leverage helper
|
|
// functions from these parameter values as nice abstractions. the following parameters are
|
|
// required for the helper functions defined in this file.
|
|
type Parameters struct {
|
|
chunkSize uint64 // C - defines how many elements are in a chunk for a validator min or max span slice.
|
|
validatorChunkSize uint64 // K - defines how many validators' chunks we store in a single flat byte slice on disk.
|
|
historyLength primitives.Epoch // H - defines how many epochs we keep of min or max spans.
|
|
}
|
|
|
|
// ChunkSize returns the chunk size.
|
|
func (p *Parameters) ChunkSize() uint64 {
|
|
return p.chunkSize
|
|
}
|
|
|
|
// ValidatorChunkSize returns the validator chunk size.
|
|
func (p *Parameters) ValidatorChunkSize() uint64 {
|
|
return p.validatorChunkSize
|
|
}
|
|
|
|
// HistoryLength returns the history length.
|
|
func (p *Parameters) HistoryLength() primitives.Epoch {
|
|
return p.historyLength
|
|
}
|
|
|
|
// DefaultParams defines default values for slasher's important parameters, defined
|
|
// based on optimization analysis for best and worst case scenarios for
|
|
// slasher's performance.
|
|
//
|
|
// The default values for chunkSize and validatorChunkSize were
|
|
// decided after an optimization analysis performed by the Sigma Prime team.
|
|
// See: https://hackmd.io/@sproul/min-max-slasher#1D-vs-2D for more information.
|
|
// We decide to keep 4096 epochs worth of data in each validator's min max spans.
|
|
func DefaultParams() *Parameters {
|
|
return &Parameters{
|
|
chunkSize: 16,
|
|
validatorChunkSize: 256,
|
|
historyLength: 4096,
|
|
}
|
|
}
|
|
|
|
func NewParams(chunkSize, validatorChunkSize uint64, historyLength primitives.Epoch) *Parameters {
|
|
return &Parameters{
|
|
chunkSize: chunkSize,
|
|
validatorChunkSize: validatorChunkSize,
|
|
historyLength: historyLength,
|
|
}
|
|
}
|
|
|
|
// ChunkIndex Validator min and max spans are split into chunks of length C = chunkSize.
|
|
// That is, if we are keeping N epochs worth of attesting history, finding what
|
|
// chunk a certain epoch, e, falls into can be computed as (e % N) / C. For example,
|
|
// if we are keeping 6 epochs worth of data, and we have chunks of size 2, then epoch
|
|
// 4 will fall into chunk index (4 % 6) / 2 = 2.
|
|
//
|
|
// span = [-, -, -, -, -, -]
|
|
// chunked = [[-, -], [-, -], [-, -]]
|
|
// |-> epoch 4, chunk idx 2
|
|
func (p *Parameters) chunkIndex(epoch primitives.Epoch) uint64 {
|
|
return uint64(epoch.Mod(uint64(p.historyLength)).Div(p.chunkSize))
|
|
}
|
|
|
|
// When storing data on disk, we take K validators' chunks. To figure out
|
|
// which validator chunk index a validator index is for, we simply divide
|
|
// the validator index, i, by K.
|
|
func (p *Parameters) validatorChunkIndex(validatorIndex primitives.ValidatorIndex) uint64 {
|
|
return uint64(validatorIndex.Div(p.validatorChunkSize))
|
|
}
|
|
|
|
// Returns the epoch at the 0th index of a chunk at the specified chunk index.
|
|
// For example, if we have chunks of length 3 and we ask to give us the
|
|
// first epoch of chunk1, then:
|
|
//
|
|
// chunk0 chunk1 chunk2
|
|
// | | |
|
|
// [[-, -, -], [-, -, -], [-, -, -], ...]
|
|
// |
|
|
// -> first epoch of chunk 1 equals 3
|
|
func (p *Parameters) firstEpoch(chunkIndex uint64) primitives.Epoch {
|
|
return primitives.Epoch(chunkIndex * p.chunkSize)
|
|
}
|
|
|
|
// Returns the epoch at the last index of a chunk at the specified chunk index.
|
|
// For example, if we have chunks of length 3 and we ask to give us the
|
|
// last epoch of chunk1, then:
|
|
//
|
|
// chunk0 chunk1 chunk2
|
|
// | | |
|
|
// [[-, -, -], [-, -, -], [-, -, -], ...]
|
|
// |
|
|
// -> last epoch of chunk 1 equals 5
|
|
func (p *Parameters) lastEpoch(chunkIndex uint64) primitives.Epoch {
|
|
return p.firstEpoch(chunkIndex).Add(p.chunkSize - 1)
|
|
}
|
|
|
|
// Given a validator index, and epoch, we compute the exact index
|
|
// into our flat slice on disk which stores K validators' chunks, each
|
|
// chunk of size C. For example, if C = 3 and K = 3, the data we store
|
|
// on disk is a flat slice as follows:
|
|
//
|
|
// val0 val1 val2
|
|
// | | |
|
|
// { } { } { }
|
|
// [-, -, -, -, -, -, -, -, -]
|
|
//
|
|
// Then, figuring out the exact cell index for epoch 1 for validator 2 is computed
|
|
// with (validatorIndex % K)*C + (epoch % C), which gives us:
|
|
//
|
|
// (2 % 3)*3 + (1 % 3) =
|
|
// 2*3 + 1 =
|
|
// 7
|
|
//
|
|
// val0 val1 val2
|
|
// | | |
|
|
// { } { } { }
|
|
// [-, -, -, -, -, -, -, -, -]
|
|
// |-> epoch 1 for val2
|
|
func (p *Parameters) cellIndex(validatorIndex primitives.ValidatorIndex, epoch primitives.Epoch) uint64 {
|
|
validatorChunkOffset := p.validatorOffset(validatorIndex)
|
|
chunkOffset := p.chunkOffset(epoch)
|
|
return validatorChunkOffset*p.chunkSize + chunkOffset
|
|
}
|
|
|
|
// Computes the start index of a chunk given an epoch.
|
|
func (p *Parameters) chunkOffset(epoch primitives.Epoch) uint64 {
|
|
return uint64(epoch.Mod(p.chunkSize))
|
|
}
|
|
|
|
// Computes the start index of a validator chunk given a validator index.
|
|
func (p *Parameters) validatorOffset(validatorIndex primitives.ValidatorIndex) uint64 {
|
|
return uint64(validatorIndex.Mod(p.validatorChunkSize))
|
|
}
|
|
|
|
// Construct a key for our database schema given a validator chunk index and chunk index.
|
|
// This calculation gives us a uint encoded as bytes that uniquely represents
|
|
// a 2D chunk given a validator index and epoch value.
|
|
// First, we compute the validator chunk index for the validator index,
|
|
// Then, we compute the chunk index for the epoch.
|
|
// If chunkSize C = 3 and validatorChunkSize K = 3, and historyLength H = 12,
|
|
// if we are looking for epoch 6 and validator 6, then
|
|
//
|
|
// validatorChunkIndex = 6 / 3 = 2
|
|
// chunkIndex = (6 % historyLength) / 3 = (6 % 12) / 3 = 2
|
|
//
|
|
// Then we compute how many chunks there are per max span, known as the "width"
|
|
//
|
|
// width = H / C = 12 / 3 = 4
|
|
//
|
|
// So every span has 4 chunks. Then, we have a disk key calculated by
|
|
//
|
|
// validatorChunkIndex * width + chunkIndex = 2*4 + 2 = 10
|
|
func (p *Parameters) flatSliceID(validatorChunkIndex, chunkIndex uint64) []byte {
|
|
width := p.historyLength.Div(p.chunkSize)
|
|
return ssz.MarshalUint64(make([]byte, 0), uint64(width.Mul(validatorChunkIndex).Add(chunkIndex)))
|
|
}
|
|
|
|
// ValidatorIndexesInChunk Given a validator chunk index, we determine all the validators
|
|
// indices that will belong in that chunk.
|
|
func (p *Parameters) ValidatorIndexesInChunk(validatorChunkIndex uint64) []primitives.ValidatorIndex {
|
|
validatorIndices := make([]primitives.ValidatorIndex, 0)
|
|
low := validatorChunkIndex * p.validatorChunkSize
|
|
high := (validatorChunkIndex + 1) * p.validatorChunkSize
|
|
|
|
for i := low; i < high; i++ {
|
|
validatorIndices = append(validatorIndices, primitives.ValidatorIndex(i))
|
|
}
|
|
|
|
return validatorIndices
|
|
}
|