Skip to content

Commit

Permalink
dkg: partial deposits enabled (ObolNetwork#2894)
Browse files Browse the repository at this point in the history
* `dkg` command supporting partial deposits (previous work added cli option `--deposit-amounts` to `create dkg` command).

One important and _not very elegant_ change was done to the `exchanger` component, specifically:
```
	// sigDepositData is responsible for deposit data signed partial signatures exchange and aggregation.
	// For partial deposits, it increments the number for each unique partial amount, e.g. 201, 202, etc.
	sigDepositData sigType = 200
```

This is because we need to execute several rounds for each partial deposit amount and we need to distinguish between each such round and store them independently (parsigdb). To make it more elegant, a drastic change to the protocol would be required, such as adding additional field to the tuple of {Duty, Slot}, I would call it a group. But when I tried to go this route, I quickly realized the viral effect of this change and too many components will be affected. Hence the simplification and the present solution. Open to discuss alternative options.

category: feature
ticket: ObolNetwork#2889
  • Loading branch information
pinebit authored Feb 27, 2024
1 parent 639492d commit 0911813
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 113 deletions.
9 changes: 1 addition & 8 deletions cmd/createcluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,16 +394,8 @@ func signDepositDatas(secrets []tbls.PrivateKey, withdrawalAddresses []string, n
return nil, errors.New("empty deposit amounts")
}

usedAmounts := make(map[eth2p0.Gwei]struct{})

var dd [][]eth2p0.DepositData
for _, depositAmount := range depositAmounts {
if _, used := usedAmounts[depositAmount]; used {
continue
}

usedAmounts[depositAmount] = struct{}{}

var datas []eth2p0.DepositData
for i, secret := range secrets {
withdrawalAddr, err := eth2util.ChecksumAddress(withdrawalAddresses[i])
Expand Down Expand Up @@ -592,6 +584,7 @@ func createDepositDatas(withdrawalAddresses []string, network string, secrets []
if len(depositAmounts) == 0 {
return nil, errors.New("empty deposit amounts")
}
depositAmounts = deposit.DedupAmounts(depositAmounts)

return signDepositDatas(secrets, withdrawalAddresses, network, depositAmounts)
}
Expand Down
5 changes: 5 additions & 0 deletions dkg/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/obolnetwork/charon/app/log"
"github.com/obolnetwork/charon/app/z"
"github.com/obolnetwork/charon/cluster"
"github.com/obolnetwork/charon/eth2util/deposit"
"github.com/obolnetwork/charon/eth2util/keymanager"
"github.com/obolnetwork/charon/eth2util/keystore"
"github.com/obolnetwork/charon/tbls"
Expand Down Expand Up @@ -85,6 +86,10 @@ func loadDefinition(ctx context.Context, conf Config) (cluster.Definition, error
}
}

if err := deposit.VerifyDepositAmounts(def.DepositAmounts); err != nil {
return cluster.Definition{}, err
}

return def, nil
}

Expand Down
112 changes: 69 additions & 43 deletions dkg/dkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,13 @@ func Run(ctx context.Context, conf Config) (err error) {
}

// Sign, exchange and aggregate Deposit Data
depositDatas, err := signAndAggDepositData(ctx, ex, shares, def.WithdrawalAddresses(), network, nodeIdx)
depositAmounts := def.DepositAmounts
if len(depositAmounts) == 0 {
depositAmounts = []eth2p0.Gwei{deposit.MaxDepositAmount}
} else {
depositAmounts = deposit.DedupAmounts(depositAmounts)
}
depositDatas, err := signAndAggDepositData(ctx, ex, shares, def.WithdrawalAddresses(), network, nodeIdx, depositAmounts)
if err != nil {
return err
}
Expand Down Expand Up @@ -338,10 +344,13 @@ func Run(ctx context.Context, conf Config) (err error) {
}
log.Debug(ctx, "Saved lock file to disk")

if err := deposit.WriteDepositDataFile(depositDatas, network, conf.DataDir); err != nil {
return err
// The loop across partial amounts (shall be unique)
for _, dd := range depositDatas {
if err := deposit.WriteDepositDataFile(dd, network, conf.DataDir); err != nil {
return err
}
log.Debug(ctx, "Saved deposit data file to disk", z.Str("filepath", deposit.GetDepositFilePath(conf.DataDir, dd[0].Amount)))
}
log.Debug(ctx, "Saved deposit data file to disk", z.Str("filepath", deposit.GetDepositFilePath(conf.DataDir, depositDatas[0].Amount)))

// Signature verification and disk key write was step 6, advance to step 7
if err := nextStepSync(ctx); err != nil {
Expand Down Expand Up @@ -535,7 +544,7 @@ func startSyncProtocol(ctx context.Context, tcpNode host.Host, key *k1.PrivateKe

// signAndAggLockHash returns cluster lock file with aggregated signature after signing, exchange and aggregation of partial signatures.
func signAndAggLockHash(ctx context.Context, shares []share, def cluster.Definition,
nodeIdx cluster.NodeIdx, ex *exchanger, depositDatas []eth2p0.DepositData, valRegs []core.VersionedSignedValidatorRegistration,
nodeIdx cluster.NodeIdx, ex *exchanger, depositDatas [][]eth2p0.DepositData, valRegs []core.VersionedSignedValidatorRegistration,
) (cluster.Lock, error) {
vals, err := createDistValidators(shares, depositDatas, valRegs)
if err != nil {
Expand Down Expand Up @@ -593,20 +602,32 @@ func signAndAggLockHash(ctx context.Context, shares []share, def cluster.Definit
}

// signAndAggDepositData returns the deposit datas for each DV after signing, exchange and aggregation of partial signatures.
func signAndAggDepositData(ctx context.Context, ex *exchanger, shares []share, withdrawalAddresses []string,
network string, nodeIdx cluster.NodeIdx,
) ([]eth2p0.DepositData, error) {
parSig, despositMsgs, err := signDepositMsgs(shares, nodeIdx.ShareIdx, withdrawalAddresses, network)
if err != nil {
return nil, err
}
func signAndAggDepositData(ctx context.Context, ex *exchanger, shares []share,
withdrawalAddresses []string, network string,
nodeIdx cluster.NodeIdx, depositAmounts []eth2p0.Gwei,
) ([][]eth2p0.DepositData, error) {
var depositDataForAmounts [][]eth2p0.DepositData

for i, amount := range depositAmounts {
parSig, despositMsgs, err := signDepositMsgs(shares, nodeIdx.ShareIdx, withdrawalAddresses, network, amount)
if err != nil {
return nil, err
}

peerSigs, err := ex.exchange(ctx, sigDepositData, parSig)
if err != nil {
return nil, err
peerSigs, err := ex.exchange(ctx, sigType(int(sigDepositData)+i), parSig)
if err != nil {
return nil, err
}

dd, err := aggDepositData(peerSigs, shares, despositMsgs, network)
if err != nil {
return nil, err
}

depositDataForAmounts = append(depositDataForAmounts, dd)
}

return aggDepositData(peerSigs, shares, despositMsgs, network)
return depositDataForAmounts, nil
}

// signAndAggValidatorRegistrations returns the pre-generated validator registrations objects after signing, exchange and aggregation of partial signatures.
Expand Down Expand Up @@ -702,7 +723,7 @@ func signLockHash(shareIdx int, shares []share, hash []byte) (core.ParSignedData
}

// signDepositMsgs returns a partially signed dataset containing signatures of the deposit message signing root.
func signDepositMsgs(shares []share, shareIdx int, withdrawalAddresses []string, network string) (core.ParSignedDataSet, map[core.PubKey]eth2p0.DepositMessage, error) {
func signDepositMsgs(shares []share, shareIdx int, withdrawalAddresses []string, network string, amount eth2p0.Gwei) (core.ParSignedDataSet, map[core.PubKey]eth2p0.DepositMessage, error) {
msgs := make(map[core.PubKey]eth2p0.DepositMessage)
set := make(core.ParSignedDataSet)
for i, share := range shares {
Expand All @@ -720,7 +741,7 @@ func signDepositMsgs(shares []share, shareIdx int, withdrawalAddresses []string,
return nil, nil, err
}

msg, err := deposit.NewMessage(pubkey, withdrawalHex, deposit.MaxDepositAmount)
msg, err := deposit.NewMessage(pubkey, withdrawalHex, amount)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -969,25 +990,21 @@ func aggValidatorRegistrations(

// createDistValidators returns a slice of distributed validators from the provided
// shares and deposit datas.
func createDistValidators(shares []share, depositDatas []eth2p0.DepositData, valRegs []core.VersionedSignedValidatorRegistration) ([]cluster.DistValidator, error) {
func createDistValidators(shares []share, depositDatas [][]eth2p0.DepositData, valRegs []core.VersionedSignedValidatorRegistration) ([]cluster.DistValidator, error) {
depositDatasMap := make(map[tbls.PublicKey][]eth2p0.DepositData, len(shares))
for amountIndex := range depositDatas {
for ddIndex := range depositDatas[amountIndex] {
dd := depositDatas[amountIndex][ddIndex]
depositDatasMap[tbls.PublicKey(dd.PublicKey)] = append(depositDatasMap[tbls.PublicKey(dd.PublicKey)], dd)
}
}

var dvs []cluster.DistValidator

for _, s := range shares {
msg := msgFromShare(s)

ddIdx := -1
for i, dd := range depositDatas {
if !bytes.Equal(msg.PubKey, dd.PublicKey[:]) {
continue
}
ddIdx = i

break
}
if ddIdx == -1 {
return nil, errors.New("deposit data not found")
}

regIdx := -1

for i, reg := range valRegs {
pubkey, err := reg.PubKey()
if err != nil {
Expand All @@ -1011,17 +1028,26 @@ func createDistValidators(shares []share, depositDatas []eth2p0.DepositData, val
return nil, err
}

var partialDepositData []cluster.DepositData

depositDatasList, ok := depositDatasMap[tbls.PublicKey(msg.PubKey)]
if !ok {
return nil, errors.New("deposit data not found for pubkey", z.Str("pubkey", hex.EncodeToString(msg.PubKey)))
}

for _, dd := range depositDatasList {
partialDepositData = append(partialDepositData, cluster.DepositData{
PubKey: dd.PublicKey[:],
WithdrawalCredentials: dd.WithdrawalCredentials,
Amount: int(dd.Amount),
Signature: dd.Signature[:],
})
}

dvs = append(dvs, cluster.DistValidator{
PubKey: msg.PubKey,
PubShares: msg.PubShares,
PartialDepositData: []cluster.DepositData{
{
PubKey: depositDatas[ddIdx].PublicKey[:],
WithdrawalCredentials: depositDatas[ddIdx].WithdrawalCredentials,
Amount: int(depositDatas[ddIdx].Amount),
Signature: depositDatas[ddIdx].Signature[:],
},
},
PubKey: msg.PubKey,
PubShares: msg.PubShares,
PartialDepositData: partialDepositData,
BuilderRegistration: reg,
})
}
Expand Down
41 changes: 33 additions & 8 deletions dkg/dkg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/obolnetwork/charon/dkg"
dkgsync "github.com/obolnetwork/charon/dkg/sync"
"github.com/obolnetwork/charon/eth2util"
"github.com/obolnetwork/charon/eth2util/deposit"
"github.com/obolnetwork/charon/eth2util/keystore"
"github.com/obolnetwork/charon/eth2util/registration"
"github.com/obolnetwork/charon/p2p"
Expand All @@ -51,12 +52,19 @@ func TestDKG(t *testing.T) {
}
}

withDepositAmounts := func(amounts []eth2p0.Gwei) func(*cluster.Definition) {
return func(d *cluster.Definition) {
d.DepositAmounts = amounts
}
}

tests := []struct {
name string
dkgAlgo string
version string // Defaults to latest if empty
keymanager bool
publish bool
name string
dkgAlgo string
version string // Defaults to latest if empty
depositAmounts []eth2p0.Gwei
keymanager bool
publish bool
}{
{
name: "frost_v16",
Expand All @@ -67,6 +75,16 @@ func TestDKG(t *testing.T) {
name: "frost_latest",
dkgAlgo: "frost",
},
{
name: "with_partial_deposits",
version: "v1.8.0",
dkgAlgo: "frost",
depositAmounts: []eth2p0.Gwei{
8 * deposit.OneEthInGwei,
16 * deposit.OneEthInGwei,
8 * deposit.OneEthInGwei,
},
},
{
name: "dkg with keymanager",
dkgAlgo: "frost",
Expand All @@ -83,6 +101,7 @@ func TestDKG(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
opts := []func(*cluster.Definition){
withAlgo(test.dkgAlgo),
withDepositAmounts(test.depositAmounts),
}
if test.version != "" {
opts = append(opts, cluster.WithVersion(test.version))
Expand Down Expand Up @@ -321,9 +340,15 @@ func verifyDKGResults(t *testing.T, def cluster.Definition, dir string) {

for j, val := range lock.Validators {
// Assert Deposit Data
require.Len(t, val.PartialDepositData, 1)
require.EqualValues(t, val.PubKey, val.PartialDepositData[0].PubKey)
require.EqualValues(t, 32_000_000_000, val.PartialDepositData[0].Amount)
depositAmounts := deposit.DedupAmounts(def.DepositAmounts)
if len(depositAmounts) == 0 {
depositAmounts = []eth2p0.Gwei{deposit.MaxDepositAmount}
}
require.Len(t, val.PartialDepositData, len(depositAmounts))
for i, amount := range depositAmounts {
require.EqualValues(t, val.PubKey, val.PartialDepositData[i].PubKey)
require.EqualValues(t, amount, val.PartialDepositData[i].Amount)
}

if !cluster.SupportPregenRegistrations(lock.Version) {
require.Empty(t, val.BuilderRegistration.Signature)
Expand Down
Loading

0 comments on commit 0911813

Please sign in to comment.