Skip to content

Commit

Permalink
Enable Multi-Quorum (Layr-Labs#204)
Browse files Browse the repository at this point in the history
  • Loading branch information
ian-shim authored Jan 24, 2024
1 parent 7bcca6c commit b2d11b5
Show file tree
Hide file tree
Showing 27 changed files with 302 additions and 293 deletions.
3 changes: 2 additions & 1 deletion api/proto/disperser/disperser.proto
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ message BatchMetadata {
message BatchHeader {
// The root of the merkle tree with the hashes of blob headers as leaves.
bytes batch_root = 1;
// All quorums associated with blobs in this batch.
// All quorums associated with blobs in this batch. Sorted in ascending order.
// Ex. [0, 2, 1] => 0x000102
bytes quorum_numbers = 2;
// The percentage of stake that has signed for this batch.
// The quorum_signed_percentages[i] is percentage for the quorum_numbers[i].
Expand Down
48 changes: 27 additions & 21 deletions clients/disperser_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ func NewConfig(hostname, port string, timeout time.Duration, useSecureGrpcFlag b
}

type DisperserClient interface {
DisperseBlob(ctx context.Context, data []byte, quorumID, quorumThreshold, adversityThreshold uint32) (*disperser.BlobStatus, []byte, error)
DisperseBlobAuthenticated(ctx context.Context, data []byte, quorumID, quorumThreshold, adversityThreshold uint32) (*disperser.BlobStatus, []byte, error)
DisperseBlob(ctx context.Context, data []byte, securityParams []*core.SecurityParam) (*disperser.BlobStatus, []byte, error)
DisperseBlobAuthenticated(ctx context.Context, data []byte, securityParams []*core.SecurityParam) (*disperser.BlobStatus, []byte, error)
GetBlobStatus(ctx context.Context, key []byte) (*disperser_rpc.BlobStatusReply, error)
}

Expand All @@ -58,7 +58,7 @@ func (c *disperserClient) getDialOptions() []grpc.DialOption {
}
}

func (c *disperserClient) DisperseBlob(ctx context.Context, data []byte, quorumID, quorumThreshold, adversityThreshold uint32) (*disperser.BlobStatus, []byte, error) {
func (c *disperserClient) DisperseBlob(ctx context.Context, data []byte, securityParams []*core.SecurityParam) (*disperser.BlobStatus, []byte, error) {
addr := fmt.Sprintf("%v:%v", c.config.Hostname, c.config.Port)

dialOptions := c.getDialOptions()
Expand All @@ -72,15 +72,18 @@ func (c *disperserClient) DisperseBlob(ctx context.Context, data []byte, quorumI
ctxTimeout, cancel := context.WithTimeout(ctx, c.config.Timeout)
defer cancel()

sp := make([]*disperser_rpc.SecurityParams, len(securityParams))
for i, s := range securityParams {
sp[i] = &disperser_rpc.SecurityParams{
QuorumId: uint32(s.QuorumID),
QuorumThreshold: uint32(s.QuorumThreshold),
AdversaryThreshold: uint32(s.AdversaryThreshold),
}
}

request := &disperser_rpc.DisperseBlobRequest{
Data: data,
SecurityParams: []*disperser_rpc.SecurityParams{
{
QuorumId: quorumID,
QuorumThreshold: quorumThreshold,
AdversaryThreshold: adversityThreshold,
},
},
Data: data,
SecurityParams: sp,
}

reply, err := disperserClient.DisperseBlob(ctxTimeout, request)
Expand All @@ -96,7 +99,7 @@ func (c *disperserClient) DisperseBlob(ctx context.Context, data []byte, quorumI
return blobStatus, reply.GetRequestId(), nil
}

func (c *disperserClient) DisperseBlobAuthenticated(ctx context.Context, data []byte, quorumID, quorumThreshold, adversityThreshold uint32) (*disperser.BlobStatus, []byte, error) {
func (c *disperserClient) DisperseBlobAuthenticated(ctx context.Context, data []byte, securityParams []*core.SecurityParam) (*disperser.BlobStatus, []byte, error) {

addr := fmt.Sprintf("%v:%v", c.config.Hostname, c.config.Port)

Expand All @@ -117,16 +120,19 @@ func (c *disperserClient) DisperseBlobAuthenticated(ctx context.Context, data []
return nil, nil, fmt.Errorf("frror while calling DisperseBlobAuthenticated: %v", err)
}

sp := make([]*disperser_rpc.SecurityParams, len(securityParams))
for i, s := range securityParams {
sp[i] = &disperser_rpc.SecurityParams{
QuorumId: uint32(s.QuorumID),
QuorumThreshold: uint32(s.QuorumThreshold),
AdversaryThreshold: uint32(s.AdversaryThreshold),
}
}

request := &disperser_rpc.DisperseBlobRequest{
Data: data,
SecurityParams: []*disperser_rpc.SecurityParams{
{
QuorumId: quorumID,
QuorumThreshold: quorumThreshold,
AdversaryThreshold: adversityThreshold,
},
},
AccountId: c.signer.GetAccountID(),
Data: data,
SecurityParams: sp,
AccountId: c.signer.GetAccountID(),
}

// Send the initial request
Expand Down
9 changes: 5 additions & 4 deletions clients/mock/disperser_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

disperser_rpc "github.com/Layr-Labs/eigenda/api/grpc/disperser"
"github.com/Layr-Labs/eigenda/clients"
"github.com/Layr-Labs/eigenda/core"
"github.com/Layr-Labs/eigenda/disperser"
"github.com/stretchr/testify/mock"
)
Expand All @@ -19,8 +20,8 @@ func NewMockDisperserClient() *MockDisperserClient {
return &MockDisperserClient{}
}

func (c *MockDisperserClient) DisperseBlobAuthenticated(ctx context.Context, data []byte, quorumID, quorumThreshold, adversityThreshold uint32) (*disperser.BlobStatus, []byte, error) {
args := c.Called(data, quorumID, quorumThreshold, adversityThreshold)
func (c *MockDisperserClient) DisperseBlobAuthenticated(ctx context.Context, data []byte, securityParams []*core.SecurityParam) (*disperser.BlobStatus, []byte, error) {
args := c.Called(data, securityParams)
var status *disperser.BlobStatus
if args.Get(0) != nil {
status = (args.Get(0)).(*disperser.BlobStatus)
Expand All @@ -36,8 +37,8 @@ func (c *MockDisperserClient) DisperseBlobAuthenticated(ctx context.Context, dat
return status, key, err
}

func (c *MockDisperserClient) DisperseBlob(ctx context.Context, data []byte, quorumID, quorumThreshold, adversityThreshold uint32) (*disperser.BlobStatus, []byte, error) {
args := c.Called(data, quorumID, quorumThreshold, adversityThreshold)
func (c *MockDisperserClient) DisperseBlob(ctx context.Context, data []byte, securityParams []*core.SecurityParam) (*disperser.BlobStatus, []byte, error) {
args := c.Called(data, securityParams)
var status *disperser.BlobStatus
if args.Get(0) != nil {
status = (args.Get(0)).(*disperser.BlobStatus)
Expand Down
307 changes: 127 additions & 180 deletions contracts/bindings/MockRollup/binding.go

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions contracts/script/MockRollupDeployer.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@ contract MockRollupDeployer is Script {

// forge script script/MockRollupDeployer.s.sol:MockRollupDeployer --sig "run(address, bytes32, uint256)" <DASM address> <security hash> <stake> --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast -vvvv
// <security hash> = keccak256(abi.encode(blobHeader.quorumBlobParams))
function run(address _eigenDAServiceManager, bytes32 _quorumBlobParamsHash, uint256 _stakeRequired) external {
function run(address _eigenDAServiceManager, uint256 _stakeRequired) external {
vm.startBroadcast();

mockRollup = new MockRollup(
IEigenDAServiceManager(_eigenDAServiceManager),
s1,
illegalValue,
_quorumBlobParamsHash,
_stakeRequired
);

Expand Down
8 changes: 1 addition & 7 deletions contracts/test/MockRollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ contract MockRollupTest is BLSMockAVSDeployer {
uint256 illegalPoint = 6;
uint256 illegalValue = 1555;
BN254.G2Point illegalProof;
bytes32 quorumBlobParamsHash = keccak256(abi.encodePacked("quorumBlobParamsHash"));

function setUp() public {
_setUpBLSMockAVSDeployer();
Expand Down Expand Up @@ -76,7 +75,7 @@ contract MockRollupTest is BLSMockAVSDeployer {
)
);

mockRollup = new MockRollup(eigenDAServiceManager, s1, illegalValue, quorumBlobParamsHash, defaultStakeRequired);
mockRollup = new MockRollup(eigenDAServiceManager, s1, illegalValue, defaultStakeRequired);

//hardcode g2 proof
illegalProof.X[1] = 11151623676041303181597631684634074376466382703418354161831688442589830350329;
Expand All @@ -95,11 +94,6 @@ contract MockRollupTest is BLSMockAVSDeployer {
//get commitment with illegal value
(IEigenDAServiceManager.BlobHeader memory blobHeader, EigenDABlobUtils.BlobVerificationProof memory blobVerificationProof) = _getCommitment(pseudoRandomNumber);

stdstore
.target(address(mockRollup))
.sig("quorumBlobParamsHash()")
.checked_write(keccak256(abi.encode(blobHeader.quorumBlobParams)));

//post commitment
vm.prank(alice);
mockRollup.postCommitment(blobHeader, blobVerificationProof);
Expand Down
7 changes: 1 addition & 6 deletions contracts/test/mocks/MockRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ contract MockRollup {
IEigenDAServiceManager public eigenDAServiceManager; // EigenDASM contract
BN254.G1Point public tau; //power of tau
uint256 public illegalValue; // special "illegal" value that should not be included in blob
bytes32 public quorumBlobParamsHash; // hash of the security parameters
uint256 public stakeRequired; // amount of stake required to register as a validator

///@notice mapping of validators who have registered
Expand All @@ -34,11 +33,10 @@ contract MockRollup {
///@notice mapping of timestamps to commitments
mapping(uint256 => Commitment) public commitments;

constructor(IEigenDAServiceManager _eigenDAServiceManager, BN254.G1Point memory _tau, uint256 _illegalValue, bytes32 _quorumBlobParamsHash, uint256 _stakeRequired) {
constructor(IEigenDAServiceManager _eigenDAServiceManager, BN254.G1Point memory _tau, uint256 _illegalValue, uint256 _stakeRequired) {
eigenDAServiceManager = _eigenDAServiceManager;
tau = _tau;
illegalValue = _illegalValue;
quorumBlobParamsHash = _quorumBlobParamsHash;
stakeRequired = _stakeRequired;
}

Expand All @@ -62,9 +60,6 @@ contract MockRollup {
require(validators[msg.sender], "MockRollup.postCommitment: Validator not registered");
require(commitments[block.timestamp].validator == address(0), "MockRollup.postCommitment: Commitment already posted");

// verify that the blob header contains the correct quorumBlobParams
require(keccak256(abi.encode(blobHeader.quorumBlobParams)) == quorumBlobParamsHash, "MockRollup.postCommitment: QuorumBlobParams do not match quorumBlobParamsHash");

// verify that the blob was included in the batch
EigenDABlobUtils.verifyBlob(blobHeader, eigenDAServiceManager, blobVerificationProof);

Expand Down
8 changes: 4 additions & 4 deletions core/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,15 @@ type BlobMessage struct {
}

// Serialize encodes a batch of chunks into a byte array
func (cb Bundles) Serialize() ([][][]byte, error) {
data := make([][][]byte, len(cb))
for i, bundle := range cb {
func (cb Bundles) Serialize() (map[uint32][][]byte, error) {
data := make(map[uint32][][]byte, len(cb))
for quorumID, bundle := range cb {
for _, chunk := range bundle {
chunkData, err := chunk.Serialize()
if err != nil {
return nil, err
}
data[i] = append(data[i], chunkData)
data[uint32(quorumID)] = append(data[uint32(quorumID)], chunkData)
}
}
return data, nil
Expand Down
21 changes: 19 additions & 2 deletions core/eth/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"math/big"
"slices"

"github.com/Layr-Labs/eigenda/api/grpc/churner"
"github.com/Layr-Labs/eigenda/common"
Expand Down Expand Up @@ -713,8 +714,16 @@ func quorumIDsToQuorumNumbers(quorumIds []core.QuorumID) []byte {

func quorumParamsToQuorumNumbers(quorumParams map[core.QuorumID]*core.QuorumResult) []byte {
quorumNumbers := make([]byte, len(quorumParams))
quorums := make([]uint8, len(quorumParams))
i := 0
for _, qp := range quorumParams {
for k := range quorumParams {
quorums[i] = k
i++
}
slices.Sort(quorums)
i = 0
for _, quorum := range quorums {
qp := quorumParams[quorum]
quorumNumbers[i] = byte(qp.QuorumID)
i++
}
Expand All @@ -723,8 +732,16 @@ func quorumParamsToQuorumNumbers(quorumParams map[core.QuorumID]*core.QuorumResu

func quorumParamsToThresholdPercentages(quorumParams map[core.QuorumID]*core.QuorumResult) []byte {
thresholdPercentages := make([]byte, len(quorumParams))
quorums := make([]uint8, len(quorumParams))
i := 0
for _, qp := range quorumParams {
for k := range quorumParams {
quorums[i] = k
i++
}
slices.Sort(quorums)
i = 0
for _, quorum := range quorums {
qp := quorumParams[quorum]
thresholdPercentages[i] = byte(qp.PercentSigned)
i++
}
Expand Down
4 changes: 4 additions & 0 deletions core/serialization.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"math/big"
"regexp"
"slices"

binding "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager"
"github.com/Layr-Labs/eigenda/pkg/kzg/bn254"
Expand Down Expand Up @@ -324,6 +325,9 @@ func (h *BlobHeader) Encode() ([]byte, error) {
QuantizationParameter: 0,
}
}
slices.SortStableFunc[[]quorumBlobParams](qbp, func(a, b quorumBlobParams) int {
return int(a.QuorumNumber) - int(b.QuorumNumber)
})

s := struct {
Commitment commitment
Expand Down
4 changes: 2 additions & 2 deletions core/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (v *chunkValidator) validateBlobQuorum(quorumHeader *BlobQuorumInfo, blob *
return nil, nil, nil, fmt.Errorf("%w: operator %s has no chunks in quorum %d", ErrBlobQuorumSkip, v.operatorID.Hex(), quorumHeader.QuorumID)
}
if assignment.NumChunks != uint(len(blob.Bundles[quorumHeader.QuorumID])) {
return nil, nil, nil, fmt.Errorf("number of chunks (%d) does not match assignment (%d)", len(blob.Bundles[quorumHeader.QuorumID]), assignment.NumChunks)
return nil, nil, nil, fmt.Errorf("number of chunks (%d) does not match assignment (%d) for quorum %d", len(blob.Bundles[quorumHeader.QuorumID]), assignment.NumChunks, quorumHeader.QuorumID)
}

// Validate the chunkLength against the quorum and adversary threshold parameters
Expand All @@ -69,7 +69,7 @@ func (v *chunkValidator) validateBlobQuorum(quorumHeader *BlobQuorumInfo, blob *
chunks := blob.Bundles[quorumHeader.QuorumID]
for _, chunk := range chunks {
if uint(chunk.Length()) != quorumHeader.ChunkLength {
return nil, nil, nil, fmt.Errorf("%w: chunk length (%d) does not match quorum header (%d)", ErrChunkLengthMismatch, chunk.Length(), quorumHeader.ChunkLength)
return nil, nil, nil, fmt.Errorf("%w: chunk length (%d) does not match quorum header (%d) for quorum %d", ErrChunkLengthMismatch, chunk.Length(), quorumHeader.ChunkLength, quorumHeader.QuorumID)
}
}

Expand Down
4 changes: 4 additions & 0 deletions disperser/apiserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"math/rand"
"net"
"slices"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -455,6 +456,9 @@ func (s *DispersalServer) GetBlobStatus(ctx context.Context, req *pb.BlobStatusR

dataLength := uint32(confirmationInfo.BlobCommitment.Length)
quorumInfos := confirmationInfo.BlobQuorumInfos
slices.SortStableFunc[[]*core.BlobQuorumInfo](quorumInfos, func(a, b *core.BlobQuorumInfo) int {
return int(a.QuorumID) - int(b.QuorumID)
})
blobQuorumParams := make([]*pb.BlobQuorumParam, len(quorumInfos))
quorumNumbers := make([]byte, len(quorumInfos))
quorumPercentSigned := make([]byte, len(quorumInfos))
Expand Down
7 changes: 4 additions & 3 deletions disperser/batcher/grpc/dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ func (c *dispatcher) sendAllChunks(ctx context.Context, state *core.IndexedOpera
for i, blob := range blobs {
blobMessages[i] = blob[id]
}

sig, err := c.sendChunks(ctx, blobMessages, header, &op)
if err != nil {
update <- core.SignerMessage{
Expand Down Expand Up @@ -155,9 +154,11 @@ func getBlobMessage(blob *core.BlobMessage) (*node.Blob, error) {
return nil, err
}
bundles := make([]*node.Bundle, len(blob.Bundles))
for i := range blob.Bundles {
// the ordering of quorums in bundles must be same as in quorumHeaders
for i, quorumHeader := range quorumHeaders {
quorum := quorumHeader.QuorumId
bundles[i] = &node.Bundle{
Chunks: data[i],
Chunks: data[quorum],
}
}

Expand Down
12 changes: 6 additions & 6 deletions inabox/deploy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,11 @@ func (env *Config) generateDisperserVars(ind int, key, address, logPath, dbPath,
DISPERSER_SERVER_PRIVATE_KEY: "123",
DISPERSER_SERVER_NUM_CONFIRMATIONS: "0",

DISPERSER_SERVER_REGISTERED_QUORUM_ID: "0",
DISPERSER_SERVER_TOTAL_UNAUTH_BYTE_RATE: "10000000",
DISPERSER_SERVER_PER_USER_UNAUTH_BYTE_RATE: "32000",
DISPERSER_SERVER_TOTAL_UNAUTH_BLOB_RATE: "10",
DISPERSER_SERVER_PER_USER_UNAUTH_BLOB_RATE: "2",
DISPERSER_SERVER_REGISTERED_QUORUM_ID: "0,1",
DISPERSER_SERVER_TOTAL_UNAUTH_BYTE_RATE: "10000000,10000000",
DISPERSER_SERVER_PER_USER_UNAUTH_BYTE_RATE: "32000,32000",
DISPERSER_SERVER_TOTAL_UNAUTH_BLOB_RATE: "10,10",
DISPERSER_SERVER_PER_USER_UNAUTH_BLOB_RATE: "2,2",
DISPERSER_SERVER_ENABLE_RATELIMITER: "true",
DISPERSER_SERVER_ALLOWLIST: "3.221.120.68/0/1000/10485760,18.214.113.214/0/1000/10485760",

Expand Down Expand Up @@ -280,7 +280,7 @@ func (env *Config) generateOperatorVars(ind int, name, key, churnerUrl, logPath,
NODE_ENABLE_NODE_API: "true",
NODE_API_PORT: nodeApiPort,
NODE_TIMEOUT: "10",
NODE_QUORUM_ID_LIST: "0",
NODE_QUORUM_ID_LIST: "0,1",
NODE_DB_PATH: dbPath,
NODE_ENABLE_TEST_MODE: "false", // using encrypted key in inabox
NODE_TEST_PRIVATE_BLS: blsKey,
Expand Down
10 changes: 6 additions & 4 deletions inabox/deploy/config_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,18 @@ type EigenDAContract struct {
RegistryCoordinatorWithIndices string `json:"blsRegistryCoordinatorWithIndices"`
}

type Stakes struct {
Total float32 `yaml:"total"`
Distribution []float32 `yaml:"distribution"`
}

type ServicesSpec struct {
Counts struct {
NumDis int `yaml:"dispersers"`
NumOpr int `yaml:"operators"`
NumMaxOperatorCount int `yaml:"maxOperatorCount"`
} `yaml:"counts"`
Stakes struct {
Total float32 `yaml:"total"`
Distribution []float32 `yaml:"distribution"`
} `yaml:"stakes"`
Stakes []Stakes `yaml:"stakes"`
BasePort int `yaml:"basePort"`
Variables Variables `yaml:"variables"`
}
Expand Down
Loading

0 comments on commit b2d11b5

Please sign in to comment.