mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-09 14:07:56 -05:00
Initial cut of modular command structure
This commit is contained in:
@@ -85,6 +85,8 @@ func persistentPreRun(cmd *cobra.Command, args []string) {
|
||||
attesterInclusionBindings()
|
||||
case "exit/verify":
|
||||
exitVerifyBindings()
|
||||
case "validator/depositdata":
|
||||
validatorDepositdataBindings()
|
||||
case "wallet/create":
|
||||
walletCreateBindings()
|
||||
}
|
||||
|
||||
16
cmd/validator/depositdata/generate.go
Normal file
16
cmd/validator/depositdata/generate.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package depositdata
|
||||
|
||||
//go:generate sszgen --path . --objs DepositData,DepositMessage
|
||||
137
cmd/validator/depositdata/input.go
Normal file
137
cmd/validator/depositdata/input.go
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package depositdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
"github.com/wealdtech/ethdo/grpc"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
string2eth "github.com/wealdtech/go-string2eth"
|
||||
)
|
||||
|
||||
type dataIn struct {
|
||||
format string
|
||||
withdrawalCredentials []byte
|
||||
amount uint64
|
||||
validatorAccounts []e2wtypes.Account
|
||||
forkVersion []byte
|
||||
domain []byte
|
||||
}
|
||||
|
||||
func input() (*dataIn, error) {
|
||||
var err error
|
||||
data := &dataIn{}
|
||||
|
||||
if viper.GetString("validatoraccount") == "" {
|
||||
return nil, errors.New("validator account is required")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
_, data.validatorAccounts, err = core.WalletAndAccountsFromPath(ctx, viper.GetString("validatoraccount"))
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to obtain validator account")
|
||||
}
|
||||
if len(data.validatorAccounts) == 0 {
|
||||
return nil, errors.New("unknown validator account")
|
||||
}
|
||||
|
||||
switch {
|
||||
case viper.GetBool("launchpad"):
|
||||
data.format = "launchpad"
|
||||
case viper.GetBool("raw"):
|
||||
data.format = "raw"
|
||||
default:
|
||||
data.format = "json"
|
||||
}
|
||||
|
||||
switch {
|
||||
case viper.GetString("withdrawalaccount") != "":
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
_, withdrawalAccount, err := core.WalletAndAccountFromPath(ctx, viper.GetString("withdrawalaccount"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain withdrawal account")
|
||||
}
|
||||
pubKey, err := core.BestPublicKey(withdrawalAccount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain public key for withdrawal account")
|
||||
}
|
||||
data.withdrawalCredentials = util.SHA256(pubKey.Marshal())
|
||||
case viper.GetString("withdrawalpubkey") != "":
|
||||
withdrawalPubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(viper.GetString("withdrawalpubkey"), "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode withdrawal public key")
|
||||
}
|
||||
if len(withdrawalPubKeyBytes) != 48 {
|
||||
return nil, errors.New("withdrawal public key must be exactly 48 bytes in length")
|
||||
}
|
||||
withdrawalPubKey, err := e2types.BLSPublicKeyFromBytes(withdrawalPubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "withdrawal public key is not valid")
|
||||
}
|
||||
data.withdrawalCredentials = util.SHA256(withdrawalPubKey.Marshal())
|
||||
default:
|
||||
return nil, errors.New("withdrawalaccount or withdrawal public key is required")
|
||||
}
|
||||
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
||||
data.withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||
|
||||
if viper.GetString("depositvalue") == "" {
|
||||
return nil, errors.New("deposit value is required")
|
||||
}
|
||||
data.amount, err = string2eth.StringToGWei(viper.GetString("depositvalue"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "deposit value is invalid")
|
||||
}
|
||||
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
||||
if data.amount < 1000000000 { // MIN_DEPOSIT_AMOUNT
|
||||
return nil, errors.New("deposit value must be at least 1 Ether")
|
||||
}
|
||||
|
||||
if viper.GetString("forkversion") != "" {
|
||||
data.forkVersion, err = hex.DecodeString(strings.TrimPrefix(viper.GetString("forkversion"), "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode fork version")
|
||||
}
|
||||
if len(data.forkVersion) != 4 {
|
||||
return nil, errors.New("fork version must be exactly 4 bytes in length")
|
||||
}
|
||||
} else {
|
||||
conn, err := grpc.Connect()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to connect to beacon node")
|
||||
}
|
||||
config, err := grpc.FetchChainConfig(conn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not connect to beacon node; supply a connection with --connection or provide a fork version with --forkversion to generate deposit data")
|
||||
}
|
||||
genesisForkVersion, exists := config["GenesisForkVersion"]
|
||||
if !exists {
|
||||
return nil, errors.New("failed to obtain genesis fork version")
|
||||
}
|
||||
data.forkVersion = genesisForkVersion.([]byte)
|
||||
}
|
||||
data.domain = e2types.Domain(e2types.DomainDeposit, data.forkVersion, e2types.ZeroGenesisValidatorsRoot)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
242
cmd/validator/depositdata/input_internal_test.go
Normal file
242
cmd/validator/depositdata/input_internal_test.go
Normal file
@@ -0,0 +1,242 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package depositdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
nd "github.com/wealdtech/go-eth2-wallet-nd/v2"
|
||||
scratch "github.com/wealdtech/go-eth2-wallet-store-scratch"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
func TestInput(t *testing.T) {
|
||||
require.NoError(t, e2types.InitBLS())
|
||||
|
||||
store := scratch.New()
|
||||
require.NoError(t, e2wallet.UseStore(store))
|
||||
testWallet, err := nd.CreateWallet(context.Background(), "Test", store, keystorev4.New())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, testWallet.(e2wtypes.WalletLocker).Unlock(context.Background(), nil))
|
||||
|
||||
viper.Set("passphrase", "pass")
|
||||
interop0, err := testWallet.(e2wtypes.WalletAccountImporter).ImportAccount(context.Background(),
|
||||
"Interop 0",
|
||||
hexToBytes("0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866"),
|
||||
[]byte("pass"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
_, err = testWallet.(e2wtypes.WalletAccountImporter).ImportAccount(context.Background(),
|
||||
"Interop 1",
|
||||
hexToBytes("0x51d0b65185db6989ab0b560d6deed19c7ead0e24b9b6372cbecb1f26bdfad000"),
|
||||
[]byte("pass"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
vars map[string]interface{}
|
||||
res *dataIn
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "Nil",
|
||||
err: "validator account is required",
|
||||
},
|
||||
{
|
||||
name: "ValidatorAccountMissing",
|
||||
vars: map[string]interface{}{
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "validator account is required",
|
||||
},
|
||||
{
|
||||
name: "ValidatorAccountUnknown",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Unknown",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "unknown validator account",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalAccountMissing",
|
||||
vars: map[string]interface{}{
|
||||
"launchpad": true,
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "withdrawalaccount or withdrawal public key is required",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalAccountUnknown",
|
||||
vars: map[string]interface{}{
|
||||
"raw": true,
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Unknown",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "failed to obtain withdrawal account: failed to obtain account: no account with name \"Unknown\"",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalPubKeyInvalid",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalpubkey": "invalid",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "failed to decode withdrawal public key: encoding/hex: invalid byte: U+0069 'i'",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalPubKeyWrongLength",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalpubkey": "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0bff",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "withdrawal public key must be exactly 48 bytes in length",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalPubKeyNotPubKey",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalpubkey": "0x089bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "withdrawal public key is not valid: failed to deserialize public key: err blsPublicKeyDeserialize 089bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b",
|
||||
},
|
||||
{
|
||||
name: "DepositValueMissing",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "deposit value is required",
|
||||
},
|
||||
{
|
||||
name: "DepositValueTooSmall",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "1000 Wei",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "deposit value must be at least 1 Ether",
|
||||
},
|
||||
{
|
||||
name: "DepositValueInvalid",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "1 groat",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "deposit value is invalid: failed to parse unit of 1 groat",
|
||||
},
|
||||
{
|
||||
name: "ForkVersionInvalid",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "invalid",
|
||||
},
|
||||
err: "failed to decode fork version: encoding/hex: invalid byte: U+0069 'i'",
|
||||
},
|
||||
{
|
||||
name: "ForkVersionWrongLength",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x0102030405",
|
||||
},
|
||||
err: "fork version must be exactly 4 bytes in length",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
res: &dataIn{
|
||||
format: "json",
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
domain: hexToBytes("0x03000000ffd2fc34e5796a643f749b0b2b908c4ca3ce58ce24a00c49329a2dc0"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GoodWithdrawalPubKey",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalpubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
res: &dataIn{
|
||||
format: "json",
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
domain: hexToBytes("0x03000000ffd2fc34e5796a643f749b0b2b908c4ca3ce58ce24a00c49329a2dc0"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
viper.Reset()
|
||||
for k, v := range test.vars {
|
||||
viper.Set(k, v)
|
||||
}
|
||||
res, err := input()
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
// Cannot compare accounts directly, so need to check each element individually.
|
||||
require.Equal(t, test.res.format, res.format)
|
||||
require.Equal(t, test.res.withdrawalCredentials, res.withdrawalCredentials)
|
||||
require.Equal(t, test.res.amount, res.amount)
|
||||
require.Equal(t, test.res.forkVersion, res.forkVersion)
|
||||
require.Equal(t, test.res.domain, res.domain)
|
||||
require.Equal(t, len(test.res.validatorAccounts), len(res.validatorAccounts))
|
||||
for i := range test.res.validatorAccounts {
|
||||
require.Equal(t, test.res.validatorAccounts[i].ID(), res.validatorAccounts[i].ID())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
171
cmd/validator/depositdata/output.go
Normal file
171
cmd/validator/depositdata/output.go
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package depositdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type dataOut struct {
|
||||
format string
|
||||
account string
|
||||
validatorPubKey []byte
|
||||
withdrawalCredentials []byte
|
||||
amount uint64
|
||||
signature []byte
|
||||
forkVersion []byte
|
||||
depositDataRoot []byte
|
||||
depositMessageRoot []byte
|
||||
}
|
||||
|
||||
func output(data []*dataOut) (string, error) {
|
||||
outputs := make([]string, 0)
|
||||
for _, datum := range data {
|
||||
if datum == nil {
|
||||
continue
|
||||
}
|
||||
var output string
|
||||
var err error
|
||||
switch datum.format {
|
||||
case "raw":
|
||||
output, err = validatorDepositDataOutputRaw(datum)
|
||||
case "launchpad":
|
||||
output, err = validatorDepositDataOutputLaunchpad(datum)
|
||||
default:
|
||||
output, err = validatorDepositDataOutputJSON(datum)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
outputs = append(outputs, output)
|
||||
}
|
||||
return fmt.Sprintf("[%s]", strings.Join(outputs, ",")), nil
|
||||
}
|
||||
|
||||
func validatorDepositDataOutputRaw(datum *dataOut) (string, error) {
|
||||
if len(datum.validatorPubKey) != 48 {
|
||||
return "", errors.New("validator public key must be 48 bytes")
|
||||
}
|
||||
if len(datum.withdrawalCredentials) != 32 {
|
||||
return "", errors.New("withdrawal credentials must be 32 bytes")
|
||||
}
|
||||
if datum.amount == 0 {
|
||||
return "", errors.New("missing amount")
|
||||
}
|
||||
if len(datum.signature) != 96 {
|
||||
return "", errors.New("signature must be 96 bytes")
|
||||
}
|
||||
if len(datum.depositMessageRoot) != 32 {
|
||||
return "", errors.New("deposit message root must be 32 bytes")
|
||||
}
|
||||
if len(datum.depositDataRoot) != 32 {
|
||||
return "", errors.New("deposit data root must be 32 bytes")
|
||||
}
|
||||
|
||||
output := fmt.Sprintf(
|
||||
`"`+
|
||||
// Function signature.
|
||||
"0x22895118"+
|
||||
// Pointer to validator public key.
|
||||
"0000000000000000000000000000000000000000000000000000000000000080"+
|
||||
// Pointer to withdrawal credentials.
|
||||
"00000000000000000000000000000000000000000000000000000000000000e0"+
|
||||
// Pointer to validator signature.
|
||||
"0000000000000000000000000000000000000000000000000000000000000120"+
|
||||
// Deposit data root.
|
||||
"%x"+
|
||||
// Validator public key (padded).
|
||||
"0000000000000000000000000000000000000000000000000000000000000030"+
|
||||
"%x00000000000000000000000000000000"+
|
||||
// Withdrawal credentials.
|
||||
"0000000000000000000000000000000000000000000000000000000000000020"+
|
||||
"%x"+
|
||||
// Deposit signature.
|
||||
"0000000000000000000000000000000000000000000000000000000000000060"+
|
||||
"%x"+
|
||||
`"`,
|
||||
datum.depositDataRoot,
|
||||
datum.validatorPubKey,
|
||||
datum.withdrawalCredentials,
|
||||
datum.signature,
|
||||
)
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func validatorDepositDataOutputLaunchpad(datum *dataOut) (string, error) {
|
||||
if len(datum.validatorPubKey) != 48 {
|
||||
return "", errors.New("validator public key must be 48 bytes")
|
||||
}
|
||||
if len(datum.withdrawalCredentials) != 32 {
|
||||
return "", errors.New("withdrawal credentials must be 32 bytes")
|
||||
}
|
||||
if datum.amount == 0 {
|
||||
return "", errors.New("missing amount")
|
||||
}
|
||||
if len(datum.signature) != 96 {
|
||||
return "", errors.New("signature must be 96 bytes")
|
||||
}
|
||||
if len(datum.depositMessageRoot) != 32 {
|
||||
return "", errors.New("deposit message root must be 32 bytes")
|
||||
}
|
||||
if len(datum.depositDataRoot) != 32 {
|
||||
return "", errors.New("deposit data root must be 32 bytes")
|
||||
}
|
||||
|
||||
output := fmt.Sprintf(`{"pubkey":"%x","withdrawal_credentials":"%x","amount":%d,"signature":"%x","deposit_message_root":"%x","deposit_data_root":"%x","fork_version":"%x"}`,
|
||||
datum.validatorPubKey,
|
||||
datum.withdrawalCredentials,
|
||||
datum.amount,
|
||||
datum.signature,
|
||||
datum.depositMessageRoot,
|
||||
datum.depositDataRoot,
|
||||
datum.forkVersion,
|
||||
)
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func validatorDepositDataOutputJSON(datum *dataOut) (string, error) {
|
||||
if datum.account == "" {
|
||||
return "", errors.New("missing account")
|
||||
}
|
||||
if len(datum.validatorPubKey) != 48 {
|
||||
return "", errors.New("validator public key must be 48 bytes")
|
||||
}
|
||||
if len(datum.withdrawalCredentials) != 32 {
|
||||
return "", errors.New("withdrawal credentials must be 32 bytes")
|
||||
}
|
||||
if len(datum.signature) != 96 {
|
||||
return "", errors.New("signature must be 96 bytes")
|
||||
}
|
||||
if datum.amount == 0 {
|
||||
return "", errors.New("missing amount")
|
||||
}
|
||||
if len(datum.depositDataRoot) != 32 {
|
||||
return "", errors.New("deposit data root must be 32 bytes")
|
||||
}
|
||||
|
||||
output := fmt.Sprintf(`{"name":"Deposit for %s","account":"%s","pubkey":"%#x","withdrawal_credentials":"%#x","signature":"%#x","value":%d,"deposit_data_root":"%#x","version":2}`,
|
||||
datum.account,
|
||||
datum.account,
|
||||
datum.validatorPubKey,
|
||||
datum.withdrawalCredentials,
|
||||
datum.signature,
|
||||
datum.amount,
|
||||
datum.depositDataRoot,
|
||||
)
|
||||
return output, nil
|
||||
}
|
||||
552
cmd/validator/depositdata/output_internal_test.go
Normal file
552
cmd/validator/depositdata/output_internal_test.go
Normal file
@@ -0,0 +1,552 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package depositdata
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func hexToBytes(input string) []byte {
|
||||
res, err := hex.DecodeString(strings.TrimPrefix(input, "0x"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func TestOutputJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dataOut []*dataOut
|
||||
res string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "Nil",
|
||||
res: "[]",
|
||||
},
|
||||
{
|
||||
name: "NilDatum",
|
||||
dataOut: []*dataOut{
|
||||
nil,
|
||||
},
|
||||
res: "[]",
|
||||
},
|
||||
{
|
||||
name: "AccountMissing",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "json",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "missing account",
|
||||
},
|
||||
{
|
||||
name: "MissingValidatorPubKey",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "json",
|
||||
account: "interop/00000",
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "validator public key must be 48 bytes",
|
||||
},
|
||||
{
|
||||
name: "MissingWithdrawalCredentials",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "json",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "withdrawal credentials must be 32 bytes",
|
||||
},
|
||||
{
|
||||
name: "SignatureMissing",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "json",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "signature must be 96 bytes",
|
||||
},
|
||||
{
|
||||
name: "AmountMissing",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "json",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "missing amount",
|
||||
},
|
||||
{
|
||||
name: "DepositDataRootMissing",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "json",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "deposit data root must be 32 bytes",
|
||||
},
|
||||
{
|
||||
name: "Single",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "json",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
res: `[{"name":"Deposit for interop/00000","account":"interop/00000","pubkey":"0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","signature":"0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","value":32000000000,"deposit_data_root":"0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","version":2}]`,
|
||||
},
|
||||
{
|
||||
name: "Double",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "json",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
{
|
||||
format: "json",
|
||||
account: "interop/00001",
|
||||
validatorPubKey: hexToBytes("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"),
|
||||
withdrawalCredentials: hexToBytes("0x00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0x911fe0766e8b79d711dde46bc2142eb51e35be99e5f7da505af9eaad85707bbb8013f0dea35e30403b3e57bb13054c1d0d389aceeba1d4160a148026212c7e017044e3ea69cd96fbd23b6aa9fd1e6f7e82494fbd5f8fc75856711a6b8998926e"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x3b51670e9f266d44c879682a230d60f0d534c64ab25ee68700fe3adb17ddfcab"),
|
||||
depositMessageRoot: hexToBytes("0xbb4b6184b25873cdf430df3838c8d3e3d16cf3dc3b214e2f3ab7df9e6d5a9b52"),
|
||||
},
|
||||
},
|
||||
res: `[{"name":"Deposit for interop/00000","account":"interop/00000","pubkey":"0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","signature":"0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","value":32000000000,"deposit_data_root":"0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","version":2},{"name":"Deposit for interop/00001","account":"interop/00001","pubkey":"0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","withdrawal_credentials":"0x00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594","signature":"0x911fe0766e8b79d711dde46bc2142eb51e35be99e5f7da505af9eaad85707bbb8013f0dea35e30403b3e57bb13054c1d0d389aceeba1d4160a148026212c7e017044e3ea69cd96fbd23b6aa9fd1e6f7e82494fbd5f8fc75856711a6b8998926e","value":32000000000,"deposit_data_root":"0x3b51670e9f266d44c879682a230d60f0d534c64ab25ee68700fe3adb17ddfcab","version":2}]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := output(test.dataOut)
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.res, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputLaunchpad(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dataOut []*dataOut
|
||||
res string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "Nil",
|
||||
res: "[]",
|
||||
},
|
||||
{
|
||||
name: "NilDatum",
|
||||
dataOut: []*dataOut{
|
||||
nil,
|
||||
},
|
||||
res: "[]",
|
||||
},
|
||||
{
|
||||
name: "MissingValidatorPubKey",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "launchpad",
|
||||
account: "interop/00000",
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "validator public key must be 48 bytes",
|
||||
},
|
||||
{
|
||||
name: "MissingWithdrawalCredentials",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "launchpad",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "withdrawal credentials must be 32 bytes",
|
||||
},
|
||||
{
|
||||
name: "SignatureMissing",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "launchpad",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "signature must be 96 bytes",
|
||||
},
|
||||
{
|
||||
name: "AmountMissing",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "launchpad",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "missing amount",
|
||||
},
|
||||
{
|
||||
name: "DepositDataRootMissing",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "launchpad",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "deposit data root must be 32 bytes",
|
||||
},
|
||||
{
|
||||
name: "DepositMessageRootMissing",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "launchpad",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
},
|
||||
},
|
||||
err: "deposit message root must be 32 bytes",
|
||||
},
|
||||
{
|
||||
name: "Single",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "launchpad",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"01020304"}]`,
|
||||
},
|
||||
{
|
||||
name: "Double",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "launchpad",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
{
|
||||
format: "launchpad",
|
||||
account: "interop/00001",
|
||||
validatorPubKey: hexToBytes("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"),
|
||||
withdrawalCredentials: hexToBytes("0x00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0x911fe0766e8b79d711dde46bc2142eb51e35be99e5f7da505af9eaad85707bbb8013f0dea35e30403b3e57bb13054c1d0d389aceeba1d4160a148026212c7e017044e3ea69cd96fbd23b6aa9fd1e6f7e82494fbd5f8fc75856711a6b8998926e"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x3b51670e9f266d44c879682a230d60f0d534c64ab25ee68700fe3adb17ddfcab"),
|
||||
depositMessageRoot: hexToBytes("0xbb4b6184b25873cdf430df3838c8d3e3d16cf3dc3b214e2f3ab7df9e6d5a9b52"),
|
||||
},
|
||||
},
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"01020304"},{"pubkey":"b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","withdrawal_credentials":"00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594","amount":32000000000,"signature":"911fe0766e8b79d711dde46bc2142eb51e35be99e5f7da505af9eaad85707bbb8013f0dea35e30403b3e57bb13054c1d0d389aceeba1d4160a148026212c7e017044e3ea69cd96fbd23b6aa9fd1e6f7e82494fbd5f8fc75856711a6b8998926e","deposit_message_root":"bb4b6184b25873cdf430df3838c8d3e3d16cf3dc3b214e2f3ab7df9e6d5a9b52","deposit_data_root":"3b51670e9f266d44c879682a230d60f0d534c64ab25ee68700fe3adb17ddfcab","fork_version":"01020304"}]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := output(test.dataOut)
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.res, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputRaw(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dataOut []*dataOut
|
||||
res string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "Nil",
|
||||
res: "[]",
|
||||
},
|
||||
{
|
||||
name: "NilDatum",
|
||||
dataOut: []*dataOut{
|
||||
nil,
|
||||
},
|
||||
res: "[]",
|
||||
},
|
||||
{
|
||||
name: "MissingValidatorPubKey",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "raw",
|
||||
account: "interop/00000",
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "validator public key must be 48 bytes",
|
||||
},
|
||||
{
|
||||
name: "MissingWithdrawalCredentials",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "raw",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "withdrawal credentials must be 32 bytes",
|
||||
},
|
||||
{
|
||||
name: "SignatureMissing",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "raw",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "signature must be 96 bytes",
|
||||
},
|
||||
{
|
||||
name: "AmountMissing",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "raw",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "missing amount",
|
||||
},
|
||||
{
|
||||
name: "DepositDataRootMissing",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "raw",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
err: "deposit data root must be 32 bytes",
|
||||
},
|
||||
{
|
||||
name: "DepositMessageRootMissing",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "raw",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
},
|
||||
},
|
||||
err: "deposit message root must be 32 bytes",
|
||||
},
|
||||
{
|
||||
name: "Single",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "raw",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
res: `["0x22895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001209e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a35540000000000000000000000000000000000000000000000000000000000000030a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b0000000000000000000000000000000000000000000000000000000000000060b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"]`,
|
||||
},
|
||||
{
|
||||
name: "Double",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "raw",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
{
|
||||
format: "raw",
|
||||
account: "interop/00001",
|
||||
validatorPubKey: hexToBytes("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"),
|
||||
withdrawalCredentials: hexToBytes("0x00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594"),
|
||||
amount: 32000000000,
|
||||
signature: hexToBytes("0x911fe0766e8b79d711dde46bc2142eb51e35be99e5f7da505af9eaad85707bbb8013f0dea35e30403b3e57bb13054c1d0d389aceeba1d4160a148026212c7e017044e3ea69cd96fbd23b6aa9fd1e6f7e82494fbd5f8fc75856711a6b8998926e"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x3b51670e9f266d44c879682a230d60f0d534c64ab25ee68700fe3adb17ddfcab"),
|
||||
depositMessageRoot: hexToBytes("0xbb4b6184b25873cdf430df3838c8d3e3d16cf3dc3b214e2f3ab7df9e6d5a9b52"),
|
||||
},
|
||||
},
|
||||
res: `["0x22895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001209e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a35540000000000000000000000000000000000000000000000000000000000000030a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b0000000000000000000000000000000000000000000000000000000000000060b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","0x22895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001203b51670e9f266d44c879682a230d60f0d534c64ab25ee68700fe3adb17ddfcab0000000000000000000000000000000000000000000000000000000000000030b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f35940000000000000000000000000000000000000000000000000000000000000060911fe0766e8b79d711dde46bc2142eb51e35be99e5f7da505af9eaad85707bbb8013f0dea35e30403b3e57bb13054c1d0d389aceeba1d4160a148026212c7e017044e3ea69cd96fbd23b6aa9fd1e6f7e82494fbd5f8fc75856711a6b8998926e"]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := output(test.dataOut)
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.res, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
79
cmd/validator/depositdata/process.go
Normal file
79
cmd/validator/depositdata/process.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package depositdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
"github.com/wealdtech/ethdo/signing"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
func process(data *dataIn) ([]*dataOut, error) {
|
||||
if data == nil {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
results := make([]*dataOut, 0)
|
||||
|
||||
for _, validatorAccount := range data.validatorAccounts {
|
||||
validatorPubKey, err := core.BestPublicKey(validatorAccount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "validator account does not provide a public key")
|
||||
}
|
||||
|
||||
depositMessage := &DepositMessage{
|
||||
PubKey: validatorPubKey.Marshal(),
|
||||
WithdrawalCredentials: data.withdrawalCredentials,
|
||||
Amount: data.amount,
|
||||
}
|
||||
depositMessageRoot, err := depositMessage.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to generate deposit message root")
|
||||
}
|
||||
|
||||
signature, err := signing.SignRoot(validatorAccount, depositMessageRoot[:], data.domain)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to sign deposit message")
|
||||
}
|
||||
|
||||
depositData := &DepositData{
|
||||
PubKey: validatorPubKey.Marshal(),
|
||||
WithdrawalCredentials: data.withdrawalCredentials,
|
||||
Value: data.amount,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
depositDataRoot, err := depositData.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to generate deposit data root")
|
||||
}
|
||||
|
||||
validatorWallet := validatorAccount.(e2wtypes.AccountWalletProvider).Wallet()
|
||||
results = append(results, &dataOut{
|
||||
format: data.format,
|
||||
account: fmt.Sprintf("%s/%s", validatorWallet.Name(), validatorAccount.Name()),
|
||||
validatorPubKey: validatorPubKey.Marshal(),
|
||||
withdrawalCredentials: data.withdrawalCredentials,
|
||||
amount: data.amount,
|
||||
signature: depositData.Signature,
|
||||
forkVersion: data.forkVersion,
|
||||
depositMessageRoot: depositMessageRoot[:],
|
||||
depositDataRoot: depositDataRoot[:],
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
132
cmd/validator/depositdata/process_internal_test.go
Normal file
132
cmd/validator/depositdata/process_internal_test.go
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package depositdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
nd "github.com/wealdtech/go-eth2-wallet-nd/v2"
|
||||
scratch "github.com/wealdtech/go-eth2-wallet-store-scratch"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
require.NoError(t, e2types.InitBLS())
|
||||
|
||||
testWallet, err := nd.CreateWallet(context.Background(), "Test", scratch.New(), keystorev4.New())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, testWallet.(e2wtypes.WalletLocker).Unlock(context.Background(), nil))
|
||||
|
||||
viper.Set("passphrase", "pass")
|
||||
interop0, err := testWallet.(e2wtypes.WalletAccountImporter).ImportAccount(context.Background(),
|
||||
"Interop 0",
|
||||
hexToBytes("0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866"),
|
||||
[]byte("pass"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
interop1, err := testWallet.(e2wtypes.WalletAccountImporter).ImportAccount(context.Background(),
|
||||
"Interop 1",
|
||||
hexToBytes("0x51d0b65185db6989ab0b560d6deed19c7ead0e24b9b6372cbecb1f26bdfad000"),
|
||||
[]byte("pass"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dataIn *dataIn
|
||||
res []*dataOut
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "Nil",
|
||||
err: "no data",
|
||||
},
|
||||
{
|
||||
name: "Single",
|
||||
dataIn: &dataIn{
|
||||
format: "raw",
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
domain: hexToBytes("0x03000000ffd2fc34e5796a643f749b0b2b908c4ca3ce58ce24a00c49329a2dc0"),
|
||||
},
|
||||
res: []*dataOut{
|
||||
{
|
||||
format: "raw",
|
||||
account: "Test/Interop 0",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
amount: 32000000000,
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Double",
|
||||
dataIn: &dataIn{
|
||||
format: "raw",
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0, interop1},
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
domain: hexToBytes("0x03000000ffd2fc34e5796a643f749b0b2b908c4ca3ce58ce24a00c49329a2dc0"),
|
||||
},
|
||||
res: []*dataOut{
|
||||
{
|
||||
format: "raw",
|
||||
account: "Test/Interop 0",
|
||||
validatorPubKey: hexToBytes("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
|
||||
amount: 32000000000,
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
signature: hexToBytes("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554"),
|
||||
depositMessageRoot: hexToBytes("0x139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6"),
|
||||
},
|
||||
{
|
||||
format: "raw",
|
||||
account: "Test/Interop 1",
|
||||
validatorPubKey: hexToBytes("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"),
|
||||
amount: 32000000000,
|
||||
withdrawalCredentials: hexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
signature: hexToBytes("0x939aedb76236c971c21227189c6a3a40d07909d19999798490294d284130a913b6f91d41d875768fb3e2ea4dcec672a316e5951272378f5df80a7c34fadb9a4d8462ee817faf50fe8b1c33e72d884fb17e71e665724f9e17bdf11f48eb6e9bfd"),
|
||||
forkVersion: hexToBytes("0x01020304"),
|
||||
depositDataRoot: hexToBytes("0x182c7708aad7027bea2f6251eddf62431fae4876ee3e55339082219ae7014443"),
|
||||
depositMessageRoot: hexToBytes("0x1dc5053486d74f5c91fa90e1e86d718d3fb42bb92e5cfdce98e994eb2bff2c46"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := process(test.dataIn)
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.res, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
47
cmd/validator/depositdata/run.go
Normal file
47
cmd/validator/depositdata/run.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package depositdata
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Run runs the validator deposit data command.
|
||||
func Run(cmd *cobra.Command) (string, error) {
|
||||
dataIn, err := input()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain input")
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
dataOut, err := process(dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
results, err := output(dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
29
cmd/validator/depositdata/types.go
Normal file
29
cmd/validator/depositdata/types.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package depositdata
|
||||
|
||||
// DepositMessage contains the information for a deposit.
|
||||
type DepositMessage struct {
|
||||
PubKey []byte `ssz-size:"48"`
|
||||
WithdrawalCredentials []byte `ssz-size:"32"`
|
||||
Amount uint64
|
||||
}
|
||||
|
||||
// DepositData is the deposit message with a signature.
|
||||
type DepositData struct {
|
||||
PubKey []byte `ssz-size:"48"`
|
||||
WithdrawalCredentials []byte `ssz-size:"32"`
|
||||
Value uint64
|
||||
Signature []byte `ssz-size:"96"`
|
||||
}
|
||||
208
cmd/validator/depositdata/types_encoding.go
Normal file
208
cmd/validator/depositdata/types_encoding.go
Normal file
@@ -0,0 +1,208 @@
|
||||
// Code generated by fastssz. DO NOT EDIT.
|
||||
package depositdata
|
||||
|
||||
import (
|
||||
ssz "github.com/ferranbt/fastssz"
|
||||
)
|
||||
|
||||
// MarshalSSZ ssz marshals the DepositMessage object
|
||||
func (d *DepositMessage) MarshalSSZ() ([]byte, error) {
|
||||
return ssz.MarshalSSZ(d)
|
||||
}
|
||||
|
||||
// MarshalSSZTo ssz marshals the DepositMessage object to a target array
|
||||
func (d *DepositMessage) MarshalSSZTo(buf []byte) (dst []byte, err error) {
|
||||
dst = buf
|
||||
|
||||
// Field (0) 'PubKey'
|
||||
if len(d.PubKey) != 48 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
dst = append(dst, d.PubKey...)
|
||||
|
||||
// Field (1) 'WithdrawalCredentials'
|
||||
if len(d.WithdrawalCredentials) != 32 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
dst = append(dst, d.WithdrawalCredentials...)
|
||||
|
||||
// Field (2) 'Amount'
|
||||
dst = ssz.MarshalUint64(dst, d.Amount)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalSSZ ssz unmarshals the DepositMessage object
|
||||
func (d *DepositMessage) UnmarshalSSZ(buf []byte) error {
|
||||
var err error
|
||||
size := uint64(len(buf))
|
||||
if size != 88 {
|
||||
return ssz.ErrSize
|
||||
}
|
||||
|
||||
// Field (0) 'PubKey'
|
||||
if cap(d.PubKey) == 0 {
|
||||
d.PubKey = make([]byte, 0, len(buf[0:48]))
|
||||
}
|
||||
d.PubKey = append(d.PubKey, buf[0:48]...)
|
||||
|
||||
// Field (1) 'WithdrawalCredentials'
|
||||
if cap(d.WithdrawalCredentials) == 0 {
|
||||
d.WithdrawalCredentials = make([]byte, 0, len(buf[48:80]))
|
||||
}
|
||||
d.WithdrawalCredentials = append(d.WithdrawalCredentials, buf[48:80]...)
|
||||
|
||||
// Field (2) 'Amount'
|
||||
d.Amount = ssz.UnmarshallUint64(buf[80:88])
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SizeSSZ returns the ssz encoded size in bytes for the DepositMessage object
|
||||
func (d *DepositMessage) SizeSSZ() (size int) {
|
||||
size = 88
|
||||
return
|
||||
}
|
||||
|
||||
// HashTreeRoot ssz hashes the DepositMessage object
|
||||
func (d *DepositMessage) HashTreeRoot() ([32]byte, error) {
|
||||
return ssz.HashWithDefaultHasher(d)
|
||||
}
|
||||
|
||||
// HashTreeRootWith ssz hashes the DepositMessage object with a hasher
|
||||
func (d *DepositMessage) HashTreeRootWith(hh *ssz.Hasher) (err error) {
|
||||
indx := hh.Index()
|
||||
|
||||
// Field (0) 'PubKey'
|
||||
if len(d.PubKey) != 48 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
hh.PutBytes(d.PubKey)
|
||||
|
||||
// Field (1) 'WithdrawalCredentials'
|
||||
if len(d.WithdrawalCredentials) != 32 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
hh.PutBytes(d.WithdrawalCredentials)
|
||||
|
||||
// Field (2) 'Amount'
|
||||
hh.PutUint64(d.Amount)
|
||||
|
||||
hh.Merkleize(indx)
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalSSZ ssz marshals the DepositData object
|
||||
func (d *DepositData) MarshalSSZ() ([]byte, error) {
|
||||
return ssz.MarshalSSZ(d)
|
||||
}
|
||||
|
||||
// MarshalSSZTo ssz marshals the DepositData object to a target array
|
||||
func (d *DepositData) MarshalSSZTo(buf []byte) (dst []byte, err error) {
|
||||
dst = buf
|
||||
|
||||
// Field (0) 'PubKey'
|
||||
if len(d.PubKey) != 48 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
dst = append(dst, d.PubKey...)
|
||||
|
||||
// Field (1) 'WithdrawalCredentials'
|
||||
if len(d.WithdrawalCredentials) != 32 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
dst = append(dst, d.WithdrawalCredentials...)
|
||||
|
||||
// Field (2) 'Value'
|
||||
dst = ssz.MarshalUint64(dst, d.Value)
|
||||
|
||||
// Field (3) 'Signature'
|
||||
if len(d.Signature) != 96 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
dst = append(dst, d.Signature...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalSSZ ssz unmarshals the DepositData object
|
||||
func (d *DepositData) UnmarshalSSZ(buf []byte) error {
|
||||
var err error
|
||||
size := uint64(len(buf))
|
||||
if size != 184 {
|
||||
return ssz.ErrSize
|
||||
}
|
||||
|
||||
// Field (0) 'PubKey'
|
||||
if cap(d.PubKey) == 0 {
|
||||
d.PubKey = make([]byte, 0, len(buf[0:48]))
|
||||
}
|
||||
d.PubKey = append(d.PubKey, buf[0:48]...)
|
||||
|
||||
// Field (1) 'WithdrawalCredentials'
|
||||
if cap(d.WithdrawalCredentials) == 0 {
|
||||
d.WithdrawalCredentials = make([]byte, 0, len(buf[48:80]))
|
||||
}
|
||||
d.WithdrawalCredentials = append(d.WithdrawalCredentials, buf[48:80]...)
|
||||
|
||||
// Field (2) 'Value'
|
||||
d.Value = ssz.UnmarshallUint64(buf[80:88])
|
||||
|
||||
// Field (3) 'Signature'
|
||||
if cap(d.Signature) == 0 {
|
||||
d.Signature = make([]byte, 0, len(buf[88:184]))
|
||||
}
|
||||
d.Signature = append(d.Signature, buf[88:184]...)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SizeSSZ returns the ssz encoded size in bytes for the DepositData object
|
||||
func (d *DepositData) SizeSSZ() (size int) {
|
||||
size = 184
|
||||
return
|
||||
}
|
||||
|
||||
// HashTreeRoot ssz hashes the DepositData object
|
||||
func (d *DepositData) HashTreeRoot() ([32]byte, error) {
|
||||
return ssz.HashWithDefaultHasher(d)
|
||||
}
|
||||
|
||||
// HashTreeRootWith ssz hashes the DepositData object with a hasher
|
||||
func (d *DepositData) HashTreeRootWith(hh *ssz.Hasher) (err error) {
|
||||
indx := hh.Index()
|
||||
|
||||
// Field (0) 'PubKey'
|
||||
if len(d.PubKey) != 48 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
hh.PutBytes(d.PubKey)
|
||||
|
||||
// Field (1) 'WithdrawalCredentials'
|
||||
if len(d.WithdrawalCredentials) != 32 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
hh.PutBytes(d.WithdrawalCredentials)
|
||||
|
||||
// Field (2) 'Value'
|
||||
hh.PutUint64(d.Value)
|
||||
|
||||
// Field (3) 'Signature'
|
||||
if len(d.Signature) != 96 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
hh.PutBytes(d.Signature)
|
||||
|
||||
hh.Merkleize(indx)
|
||||
return
|
||||
}
|
||||
@@ -14,29 +14,13 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/prysmaticlabs/go-ssz"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/grpc"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
string2eth "github.com/wealdtech/go-string2eth"
|
||||
validatordepositdata "github.com/wealdtech/ethdo/cmd/validator/depositdata"
|
||||
)
|
||||
|
||||
var validatorDepositDataValidatorAccount string
|
||||
var validatorDepositDataWithdrawalAccount string
|
||||
var validatorDepositDataWithdrawalPubKey string
|
||||
var validatorDepositDataDepositValue string
|
||||
var validatorDepositDataRaw bool
|
||||
var validatorDepositDataForkVersion string
|
||||
var validatorDepositDataLaunchpad bool
|
||||
|
||||
var validatorDepositDataCmd = &cobra.Command{
|
||||
Use: "depositdata",
|
||||
Short: "Generate deposit data for one or more validators",
|
||||
@@ -49,182 +33,51 @@ If validatoraccount is provided with an account path it will generate deposit da
|
||||
The information generated can be passed to ethereal to create a deposit from the Ethereum 1 chain.
|
||||
|
||||
In quiet mode this will return 0 if the the data can be generated correctly, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
assert(validatorDepositDataValidatorAccount != "", "--validatoraccount is required")
|
||||
validatorWallet, validatorAccounts, err := walletAndAccountsFromPath(ctx, validatorDepositDataValidatorAccount)
|
||||
errCheck(err, "Failed to obtain validator accounts")
|
||||
assert(len(validatorAccounts) > 0, "Failed to obtain validator account")
|
||||
|
||||
for _, validatorAccount := range validatorAccounts {
|
||||
outputIf(verbose, fmt.Sprintf("Creating deposit for %s/%s", validatorWallet.Name(), validatorAccount.Name()))
|
||||
pubKey, err := bestPublicKey(validatorAccount)
|
||||
errCheck(err, "Validator account does not provide a public key")
|
||||
outputIf(debug, fmt.Sprintf("Validator public key is %#x", pubKey.Marshal()))
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
res, err := validatordepositdata.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
assert(validatorDepositDataWithdrawalAccount != "" || validatorDepositDataWithdrawalPubKey != "", "--withdrawalaccount or --withdrawalpubkey is required")
|
||||
var withdrawalCredentials []byte
|
||||
if validatorDepositDataWithdrawalAccount != "" {
|
||||
_, withdrawalAccount, err := walletAndAccountFromPath(ctx, validatorDepositDataWithdrawalAccount)
|
||||
errCheck(err, "Failed to obtain withdrawal account")
|
||||
pubKey, err := bestPublicKey(withdrawalAccount)
|
||||
errCheck(err, "Withdrawal account does not provide a public key")
|
||||
outputIf(debug, fmt.Sprintf("Withdrawal public key is %#x", pubKey.Marshal()))
|
||||
withdrawalCredentials = util.SHA256(pubKey.Marshal())
|
||||
errCheck(err, "Failed to hash withdrawal credentials")
|
||||
} else {
|
||||
withdrawalPubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(validatorDepositDataWithdrawalPubKey, "0x"))
|
||||
errCheck(err, "Invalid withdrawal public key")
|
||||
assert(len(withdrawalPubKeyBytes) == 48, "Public key should be 48 bytes")
|
||||
withdrawalPubKey, err := e2types.BLSPublicKeyFromBytes(withdrawalPubKeyBytes)
|
||||
errCheck(err, "Value supplied with --withdrawalpubkey is not a valid public key")
|
||||
withdrawalCredentials = util.SHA256(withdrawalPubKey.Marshal())
|
||||
errCheck(err, "Failed to hash withdrawal credentials")
|
||||
}
|
||||
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
||||
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||
outputIf(debug, fmt.Sprintf("Withdrawal credentials are %#x", withdrawalCredentials))
|
||||
|
||||
assert(validatorDepositDataDepositValue != "", "--depositvalue is required")
|
||||
val, err := string2eth.StringToGWei(validatorDepositDataDepositValue)
|
||||
errCheck(err, "Invalid value")
|
||||
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
||||
assert(val >= 1000000000, "deposit value must be at least 1 Ether") // MIN_DEPOSIT_AMOUNT
|
||||
|
||||
// For each key, generate deposit data
|
||||
outputs := make([]string, 0)
|
||||
for _, validatorAccount := range validatorAccounts {
|
||||
validatorPubKey, err := bestPublicKey(validatorAccount)
|
||||
errCheck(err, "Validator account does not provide a public key")
|
||||
depositData := struct {
|
||||
PubKey []byte `ssz-size:"48"`
|
||||
WithdrawalCredentials []byte `ssz-size:"32"`
|
||||
Value uint64
|
||||
}{
|
||||
PubKey: validatorPubKey.Marshal(),
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Value: val,
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Deposit data:\n\tPublic key: %x\n\tWithdrawal credentials: %x\n\tValue: %d", depositData.PubKey, depositData.WithdrawalCredentials, depositData.Value))
|
||||
|
||||
var forkVersion []byte
|
||||
if validatorDepositDataForkVersion != "" {
|
||||
forkVersion, err = hex.DecodeString(strings.TrimPrefix(validatorDepositDataForkVersion, "0x"))
|
||||
errCheck(err, fmt.Sprintf("Failed to decode fork version %s", validatorDepositDataForkVersion))
|
||||
assert(len(forkVersion) == 4, "Fork version must be exactly four bytes")
|
||||
} else {
|
||||
err := connect()
|
||||
errCheck(err, "Failed to connect to beacon node")
|
||||
config, err := grpc.FetchChainConfig(eth2GRPCConn)
|
||||
if err != nil {
|
||||
outputIf(!quiet, "Could not connect to beacon node; supply a connection with --connection or provide a fork version with --forkversion to generate a deposit")
|
||||
os.Exit(_exitFailure)
|
||||
}
|
||||
genesisForkVersion, exists := config["GenesisForkVersion"]
|
||||
assert(exists, "Failed to obtain genesis fork version")
|
||||
forkVersion = genesisForkVersion.([]byte)
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Fork version is %x", forkVersion))
|
||||
|
||||
domain := e2types.Domain(e2types.DomainDeposit, forkVersion, e2types.ZeroGenesisValidatorsRoot)
|
||||
outputIf(debug, fmt.Sprintf("Domain is %x", domain))
|
||||
signature, err := signStruct(validatorAccount, depositData, domain)
|
||||
errCheck(err, "Failed to generate deposit data signature")
|
||||
|
||||
signedDepositData := struct {
|
||||
PubKey []byte `ssz-size:"48"`
|
||||
WithdrawalCredentials []byte `ssz-size:"32"`
|
||||
Value uint64
|
||||
Signature []byte `ssz-size:"96"`
|
||||
}{
|
||||
PubKey: validatorPubKey.Marshal(),
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Value: val,
|
||||
Signature: signature.Marshal(),
|
||||
}
|
||||
if debug {
|
||||
fmt.Printf("Signed deposit data:\n")
|
||||
fmt.Printf(" Public key: %#x\n", signedDepositData.PubKey)
|
||||
fmt.Printf(" Withdrawal credentials: %#x\n", signedDepositData.WithdrawalCredentials)
|
||||
fmt.Printf(" Value: %d\n", signedDepositData.Value)
|
||||
fmt.Printf(" Signature: %#x\n", signedDepositData.Signature)
|
||||
}
|
||||
|
||||
depositDataRoot, err := ssz.HashTreeRoot(signedDepositData)
|
||||
errCheck(err, "Failed to generate deposit data root")
|
||||
outputIf(debug, fmt.Sprintf("Deposit data root is %x", depositDataRoot))
|
||||
|
||||
switch {
|
||||
case validatorDepositDataRaw:
|
||||
// Build a raw transaction by hand
|
||||
txData := []byte{0x22, 0x89, 0x51, 0x18}
|
||||
// Pointer to validator public key
|
||||
txData = append(txData, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}...)
|
||||
// Pointer to withdrawal credentials
|
||||
txData = append(txData, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0}...)
|
||||
// Pointer to validator signature
|
||||
txData = append(txData, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20}...)
|
||||
// Deposit data root
|
||||
txData = append(txData, depositDataRoot[:]...)
|
||||
// Validator public key (pad to 32-byte boundary)
|
||||
txData = append(txData, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30}...)
|
||||
txData = append(txData, validatorPubKey.Marshal()...)
|
||||
txData = append(txData, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)
|
||||
// Withdrawal credentials
|
||||
txData = append(txData, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20}...)
|
||||
txData = append(txData, withdrawalCredentials...)
|
||||
// Deposit signature
|
||||
txData = append(txData, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60}...)
|
||||
txData = append(txData, signedDepositData.Signature...)
|
||||
outputs = append(outputs, fmt.Sprintf("%#x", txData))
|
||||
case validatorDepositDataLaunchpad:
|
||||
depositMessage := struct {
|
||||
PubKey []byte `ssz-size:"48"`
|
||||
WithdrawalCredentials []byte `ssz-size:"32"`
|
||||
Value uint64
|
||||
}{
|
||||
PubKey: validatorPubKey.Marshal(),
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Value: val,
|
||||
}
|
||||
depositMessageRoot, err := ssz.HashTreeRoot(depositMessage)
|
||||
errCheck(err, "Failed to generate deposit message root")
|
||||
outputs = append(outputs, fmt.Sprintf(`{"pubkey":"%x","withdrawal_credentials":"%x","amount":%d,"signature":"%x","deposit_message_root":"%x","deposit_data_root":"%x","fork_version":"%x"}`, signedDepositData.PubKey, signedDepositData.WithdrawalCredentials, val, signedDepositData.Signature, depositMessageRoot, depositDataRoot, forkVersion))
|
||||
default:
|
||||
outputs = append(outputs, fmt.Sprintf(`{"name":"Deposit for %s","account":"%s","pubkey":"%#x","withdrawal_credentials":"%#x","signature":"%#x","value":%d,"deposit_data_root":"%#x","version":2}`, fmt.Sprintf("%s/%s", validatorWallet.Name(), validatorAccount.Name()), fmt.Sprintf("%s/%s", validatorWallet.Name(), validatorAccount.Name()), signedDepositData.PubKey, signedDepositData.WithdrawalCredentials, signedDepositData.Signature, val, depositDataRoot))
|
||||
}
|
||||
}
|
||||
|
||||
if quiet {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if len(outputs) == 1 {
|
||||
if validatorDepositDataLaunchpad {
|
||||
// Launchpad requires an array even if there is only a single element.
|
||||
fmt.Printf("[%s]\n", outputs[0])
|
||||
} else {
|
||||
fmt.Printf("%s\n", outputs[0])
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("[")
|
||||
fmt.Print(strings.Join(outputs, ","))
|
||||
fmt.Println("]")
|
||||
if viper.GetBool("quiet") {
|
||||
return nil
|
||||
}
|
||||
fmt.Println(res)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
validatorCmd.AddCommand(validatorDepositDataCmd)
|
||||
validatorFlags(validatorDepositDataCmd)
|
||||
validatorDepositDataCmd.Flags().StringVar(&validatorDepositDataValidatorAccount, "validatoraccount", "", "Account of the account carrying out the validation")
|
||||
validatorDepositDataCmd.Flags().StringVar(&validatorDepositDataWithdrawalAccount, "withdrawalaccount", "", "Account of the account to which the validator funds will be withdrawn")
|
||||
validatorDepositDataCmd.Flags().StringVar(&validatorDepositDataWithdrawalPubKey, "withdrawalpubkey", "", "Public key of the account to which the validator funds will be withdrawn")
|
||||
validatorDepositDataCmd.Flags().StringVar(&validatorDepositDataDepositValue, "depositvalue", "", "Value of the amount to be deposited")
|
||||
validatorDepositDataCmd.Flags().BoolVar(&validatorDepositDataRaw, "raw", false, "Print raw deposit data transaction data")
|
||||
validatorDepositDataCmd.Flags().StringVar(&validatorDepositDataForkVersion, "forkversion", "", "Use a hard-coded fork version (default is to fetch it from the node)")
|
||||
validatorDepositDataCmd.Flags().BoolVar(&validatorDepositDataLaunchpad, "launchpad", false, "Print launchpad-compatible JSON")
|
||||
validatorDepositDataCmd.Flags().String("validatoraccount", "", "Account of the account carrying out the validation")
|
||||
validatorDepositDataCmd.Flags().String("withdrawalaccount", "", "Account of the account to which the validator funds will be withdrawn")
|
||||
validatorDepositDataCmd.Flags().String("withdrawalpubkey", "", "Public key of the account to which the validator funds will be withdrawn")
|
||||
validatorDepositDataCmd.Flags().String("depositvalue", "", "Value of the amount to be deposited")
|
||||
validatorDepositDataCmd.Flags().Bool("raw", false, "Print raw deposit data transaction data")
|
||||
validatorDepositDataCmd.Flags().String("forkversion", "", "Use a hard-coded fork version (default is to fetch it from the node)")
|
||||
validatorDepositDataCmd.Flags().Bool("launchpad", false, "Print launchpad-compatible JSON")
|
||||
}
|
||||
|
||||
func validatorDepositdataBindings() {
|
||||
if err := viper.BindPFlag("validatoraccount", validatorDepositDataCmd.Flags().Lookup("validatoraccount")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("withdrawalaccount", validatorDepositDataCmd.Flags().Lookup("withdrawalaccount")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("withdrawalpubkey", validatorDepositDataCmd.Flags().Lookup("withdrawalpubkey")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("depositvalue", validatorDepositDataCmd.Flags().Lookup("depositvalue")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("raw", validatorDepositDataCmd.Flags().Lookup("raw")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("forkversion", validatorDepositDataCmd.Flags().Lookup("forkversion")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("launchpad", validatorDepositDataCmd.Flags().Lookup("launchpad")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
233
core/account.go
Normal file
233
core/account.go
Normal file
@@ -0,0 +1,233 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
dirk "github.com/wealdtech/go-eth2-wallet-dirk"
|
||||
filesystem "github.com/wealdtech/go-eth2-wallet-store-filesystem"
|
||||
s3 "github.com/wealdtech/go-eth2-wallet-store-s3"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
func setup() error {
|
||||
var store e2wtypes.Store
|
||||
var err error
|
||||
if viper.GetString("remote") != "" {
|
||||
// We are using a remote account manager, so no local setup required.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set up our wallet store.
|
||||
switch viper.GetString("store") {
|
||||
case "s3":
|
||||
if viper.GetString("base-dir") != "" {
|
||||
return errors.New("basedir does not apply to the s3 store")
|
||||
}
|
||||
store, err = s3.New(s3.WithPassphrase([]byte(util.GetStorePassphrase())))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to access Amazon S3 wallet store")
|
||||
}
|
||||
case "filesystem":
|
||||
opts := make([]filesystem.Option, 0)
|
||||
if util.GetStorePassphrase() != "" {
|
||||
opts = append(opts, filesystem.WithPassphrase([]byte(util.GetStorePassphrase())))
|
||||
}
|
||||
if viper.GetString("base-dir") != "" {
|
||||
opts = append(opts, filesystem.WithLocation(viper.GetString("base-dir")))
|
||||
}
|
||||
store = filesystem.New(opts...)
|
||||
default:
|
||||
return fmt.Errorf("unsupported wallet store %s", viper.GetString("store"))
|
||||
}
|
||||
if err := e2wallet.UseStore(store); err != nil {
|
||||
return errors.Wrap(err, "failed to use defined wallet store")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WalletFromInput obtains a wallet given the information in the viper variable
|
||||
// "account", or if not present the viper variable "wallet".
|
||||
func WalletFromInput(ctx context.Context) (e2wtypes.Wallet, error) {
|
||||
if viper.GetString("account") != "" {
|
||||
return WalletFromPath(ctx, viper.GetString("account"))
|
||||
}
|
||||
return WalletFromPath(ctx, viper.GetString("wallet"))
|
||||
}
|
||||
|
||||
// WalletFromPath obtains a wallet given a path specification.
|
||||
func WalletFromPath(ctx context.Context, path string) (e2wtypes.Wallet, error) {
|
||||
walletName, _, err := e2wallet.WalletAndAccountNames(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if viper.GetString("remote") != "" {
|
||||
if viper.GetString("client-cert") == "" {
|
||||
return nil, errors.New("remote connections require client-cert")
|
||||
}
|
||||
if viper.GetString("client-key") == "" {
|
||||
return nil, errors.New("remote connections require client-key")
|
||||
}
|
||||
credentials, err := dirk.ComposeCredentials(ctx, viper.GetString("client-cert"), viper.GetString("client-key"), viper.GetString("server-ca-cert"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build dirk credentials")
|
||||
}
|
||||
|
||||
endpoints, err := remotesToEndpoints([]string{viper.GetString("remote")})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse remote servers")
|
||||
}
|
||||
|
||||
return dirk.OpenWallet(ctx, walletName, credentials, endpoints)
|
||||
}
|
||||
wallet, err := e2wallet.OpenWallet(walletName)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "failed to decrypt wallet") {
|
||||
return nil, errors.New("Incorrect store passphrase")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return wallet, nil
|
||||
}
|
||||
|
||||
// WalletAndAccountFromInput obtains the wallet and account given the information in the viper variable "account".
|
||||
func WalletAndAccountFromInput(ctx context.Context) (e2wtypes.Wallet, e2wtypes.Account, error) {
|
||||
return WalletAndAccountFromPath(ctx, viper.GetString("account"))
|
||||
}
|
||||
|
||||
// WalletAndAccountFromPath obtains the wallet and account given a path specification.
|
||||
func WalletAndAccountFromPath(ctx context.Context, path string) (e2wtypes.Wallet, e2wtypes.Account, error) {
|
||||
wallet, err := WalletFromPath(ctx, path)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "faild to open wallet for account")
|
||||
}
|
||||
_, accountName, err := e2wallet.WalletAndAccountNames(path)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to obtain account name")
|
||||
}
|
||||
if accountName == "" {
|
||||
return nil, nil, errors.New("no account name")
|
||||
}
|
||||
|
||||
if wallet.Type() == "hierarchical deterministic" && strings.HasPrefix(accountName, "m/") {
|
||||
if util.GetWalletPassphrase() == "" {
|
||||
return nil, nil, errors.New("walletpassphrase is required for direct path derivations")
|
||||
}
|
||||
|
||||
locker, isLocker := wallet.(e2wtypes.WalletLocker)
|
||||
if isLocker {
|
||||
err = locker.Unlock(ctx, []byte(viper.GetString("wallet-passphrase")))
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("failed to unlock wallet")
|
||||
}
|
||||
defer relockAccount(locker)
|
||||
}
|
||||
}
|
||||
|
||||
accountByNameProvider, isAccountByNameProvider := wallet.(e2wtypes.WalletAccountByNameProvider)
|
||||
if !isAccountByNameProvider {
|
||||
return nil, nil, errors.New("wallet cannot obtain accounts by name")
|
||||
}
|
||||
account, err := accountByNameProvider.AccountByName(ctx, accountName)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
return wallet, account, nil
|
||||
}
|
||||
|
||||
// WalletAndAccountsFromPath obtains the wallet and matching accounts given a path specification.
|
||||
func WalletAndAccountsFromPath(ctx context.Context, path string) (e2wtypes.Wallet, []e2wtypes.Account, error) {
|
||||
wallet, err := WalletFromPath(ctx, path)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "faild to open wallet for account")
|
||||
}
|
||||
|
||||
_, accountSpec, err := e2wallet.WalletAndAccountNames(path)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to obtain account specification")
|
||||
}
|
||||
if accountSpec == "" {
|
||||
accountSpec = "^.*$"
|
||||
} else {
|
||||
accountSpec = fmt.Sprintf("^%s$", accountSpec)
|
||||
}
|
||||
re := regexp.MustCompile(accountSpec)
|
||||
|
||||
accounts := make([]e2wtypes.Account, 0)
|
||||
for account := range wallet.Accounts(ctx) {
|
||||
if re.Match([]byte(account.Name())) {
|
||||
accounts = append(accounts, account)
|
||||
}
|
||||
}
|
||||
|
||||
// Tidy up accounts by name.
|
||||
sort.Slice(accounts, func(i, j int) bool {
|
||||
return accounts[i].Name() < accounts[j].Name()
|
||||
})
|
||||
|
||||
return wallet, accounts, nil
|
||||
}
|
||||
|
||||
// BestPublicKey returns the best public key for operations.
|
||||
// It prefers the composite public key if present, otherwise the public key.
|
||||
func BestPublicKey(account e2wtypes.Account) (e2types.PublicKey, error) {
|
||||
var pubKey e2types.PublicKey
|
||||
publicKeyProvider, isCompositePublicKeyProvider := account.(e2wtypes.AccountCompositePublicKeyProvider)
|
||||
if isCompositePublicKeyProvider {
|
||||
pubKey = publicKeyProvider.CompositePublicKey()
|
||||
} else {
|
||||
publicKeyProvider, isPublicKeyProvider := account.(e2wtypes.AccountPublicKeyProvider)
|
||||
if isPublicKeyProvider {
|
||||
pubKey = publicKeyProvider.PublicKey()
|
||||
} else {
|
||||
return nil, errors.New("account does not provide a public key")
|
||||
}
|
||||
}
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
// relockAccount locks an account; generally called as a defer after an account is unlocked so handles its own error.
|
||||
func relockAccount(locker e2wtypes.AccountLocker) {
|
||||
if err := locker.Lock(context.Background()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// remotesToEndpoints generates endpoints from remote addresses.
|
||||
func remotesToEndpoints(remotes []string) ([]*dirk.Endpoint, error) {
|
||||
endpoints := make([]*dirk.Endpoint, 0)
|
||||
for _, remote := range remotes {
|
||||
parts := strings.Split(remote, ":")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid remote %q", remote)
|
||||
}
|
||||
port, err := strconv.ParseUint(parts[1], 10, 32)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid port in remote %q", remote))
|
||||
}
|
||||
endpoints = append(endpoints, dirk.NewEndpoint(parts[0], uint32(port)))
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
3
go.mod
3
go.mod
@@ -4,6 +4,7 @@ go 1.13
|
||||
|
||||
require (
|
||||
github.com/OneOfOne/xxhash v1.2.5 // indirect
|
||||
github.com/ferranbt/fastssz v0.0.0-20200826142241-3a913c5a1313
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gogo/protobuf v1.3.1
|
||||
github.com/google/uuid v1.1.2
|
||||
@@ -18,6 +19,7 @@ require (
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20201003171600-a72e5f77d233
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20200618145306-2ae0807bef65
|
||||
github.com/prysmaticlabs/go-ssz v0.0.0-20200612203617-6d5c9aa213ae
|
||||
github.com/rs/zerolog v1.20.0
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/spf13/afero v1.4.1 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
@@ -40,6 +42,7 @@ require (
|
||||
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.3.1
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.16.1
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.9.0
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.6.0
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.0
|
||||
github.com/wealdtech/go-string2eth v1.1.0
|
||||
golang.org/x/text v0.3.3
|
||||
|
||||
5
go.sum
5
go.sum
@@ -291,6 +291,9 @@ github.com/prysmaticlabs/go-ssz v0.0.0-20200612203617-6d5c9aa213ae/go.mod h1:Vec
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
|
||||
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y=
|
||||
@@ -443,6 +446,7 @@ github.com/wealdtech/go-eth2-wallet-store-s3 v1.9.0/go.mod h1:dcQPLsRRYDiMV0DFYz
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.4.2 h1:GvG3ZuzxbqFjGUaGoa8Tz7XbPlDA33G6nHQbSZInC3g=
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.4.2/go.mod h1:+TbqLmJuT98PWi/xW1bp5nwZbKz+SIJYVh/+NUkmnb4=
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.5.0/go.mod h1:RMIIV5/N8TgukTVzyumQd7AplpC440ZXDSk8VffeEwQ=
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.6.0 h1:41H6hnVsI/csBx20UHpI2pY922N7Vhcro35DFS+slj0=
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.6.0/go.mod h1:XtXHbl4OV/XenQsvGmXbh+bVXaGS788oa30DB7kDInA=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.2.0 h1:SfoBlW2LYjW05uHhnTZaezX37gbRsp+VYtxWT6SeAME=
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.2.0/go.mod h1:XEvrlKFnHLbg1tj4Dep76XKASeS13TBpvdeXmvLiH+k=
|
||||
@@ -598,6 +602,7 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
||||
41
grpc/connect.go
Normal file
41
grpc/connect.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Connect connects to an Ethereum 2 endpoint.
|
||||
func Connect() (*grpc.ClientConn, error) {
|
||||
connection := ""
|
||||
if viper.GetString("connection") != "" {
|
||||
connection = viper.GetString("connection")
|
||||
}
|
||||
|
||||
if connection == "" {
|
||||
return nil, errors.New("no connection")
|
||||
}
|
||||
// outputIf(debug, fmt.Sprintf("Connecting to %s", connection))
|
||||
|
||||
opts := []grpc.DialOption{grpc.WithInsecure()}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
return grpc.DialContext(ctx, connection, opts...)
|
||||
}
|
||||
20
signing/container.go
Normal file
20
signing/container.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package signing
|
||||
|
||||
// Container contains a root and domain to sign.
|
||||
type Container struct {
|
||||
Root []byte `ssz-size:"32"`
|
||||
Domain []byte `ssz-size:"32"`
|
||||
}
|
||||
88
signing/container_encoding.go
Normal file
88
signing/container_encoding.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Code generated by fastssz. DO NOT EDIT.
|
||||
package signing
|
||||
|
||||
import (
|
||||
ssz "github.com/ferranbt/fastssz"
|
||||
)
|
||||
|
||||
// MarshalSSZ ssz marshals the Container object
|
||||
func (c *Container) MarshalSSZ() ([]byte, error) {
|
||||
return ssz.MarshalSSZ(c)
|
||||
}
|
||||
|
||||
// MarshalSSZTo ssz marshals the Container object to a target array
|
||||
func (c *Container) MarshalSSZTo(buf []byte) (dst []byte, err error) {
|
||||
dst = buf
|
||||
|
||||
// Field (0) 'Root'
|
||||
if len(c.Root) != 32 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
dst = append(dst, c.Root...)
|
||||
|
||||
// Field (1) 'Domain'
|
||||
if len(c.Domain) != 32 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
dst = append(dst, c.Domain...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalSSZ ssz unmarshals the Container object
|
||||
func (c *Container) UnmarshalSSZ(buf []byte) error {
|
||||
var err error
|
||||
size := uint64(len(buf))
|
||||
if size != 64 {
|
||||
return ssz.ErrSize
|
||||
}
|
||||
|
||||
// Field (0) 'Root'
|
||||
if cap(c.Root) == 0 {
|
||||
c.Root = make([]byte, 0, len(buf[0:32]))
|
||||
}
|
||||
c.Root = append(c.Root, buf[0:32]...)
|
||||
|
||||
// Field (1) 'Domain'
|
||||
if cap(c.Domain) == 0 {
|
||||
c.Domain = make([]byte, 0, len(buf[32:64]))
|
||||
}
|
||||
c.Domain = append(c.Domain, buf[32:64]...)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SizeSSZ returns the ssz encoded size in bytes for the Container object
|
||||
func (c *Container) SizeSSZ() (size int) {
|
||||
size = 64
|
||||
return
|
||||
}
|
||||
|
||||
// HashTreeRoot ssz hashes the Container object
|
||||
func (c *Container) HashTreeRoot() ([32]byte, error) {
|
||||
return ssz.HashWithDefaultHasher(c)
|
||||
}
|
||||
|
||||
// HashTreeRootWith ssz hashes the Container object with a hasher
|
||||
func (c *Container) HashTreeRootWith(hh *ssz.Hasher) (err error) {
|
||||
indx := hh.Index()
|
||||
|
||||
// Field (0) 'Root'
|
||||
if len(c.Root) != 32 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
hh.PutBytes(c.Root)
|
||||
|
||||
// Field (1) 'Domain'
|
||||
if len(c.Domain) != 32 {
|
||||
err = ssz.ErrBytesLength
|
||||
return
|
||||
}
|
||||
hh.PutBytes(c.Domain)
|
||||
|
||||
hh.Merkleize(indx)
|
||||
return
|
||||
}
|
||||
16
signing/generate.go
Normal file
16
signing/generate.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package signing
|
||||
|
||||
//go:generate sszgen --path . --objs Container
|
||||
69
signing/misc.go
Normal file
69
signing/misc.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package signing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
// unlock attempts to unlock an account. It returns true if the account was already unlocked.
|
||||
func unlock(account e2wtypes.Account) (bool, error) {
|
||||
locker, isAccountLocker := account.(e2wtypes.AccountLocker)
|
||||
if !isAccountLocker {
|
||||
// outputIf(debug, "Account does not support unlocking")
|
||||
// This account doesn't support unlocking; return okay.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
alreadyUnlocked, err := locker.IsUnlocked(ctx)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "unable to ascertain if account is unlocked")
|
||||
}
|
||||
|
||||
if alreadyUnlocked {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Not already unlocked; attempt to unlock it.
|
||||
for _, passphrase := range util.GetPassphrases() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
err = locker.Unlock(ctx, []byte(passphrase))
|
||||
cancel()
|
||||
if err == nil {
|
||||
// Unlocked.
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Failed to unlock it.
|
||||
return false, errors.New("failed to unlock account")
|
||||
}
|
||||
|
||||
// lock attempts to lock an account.
|
||||
func lock(account e2wtypes.Account) error {
|
||||
locker, isAccountLocker := account.(e2wtypes.AccountLocker)
|
||||
if !isAccountLocker {
|
||||
return nil
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
return locker.Lock(ctx)
|
||||
}
|
||||
95
signing/signroot.go
Normal file
95
signing/signroot.go
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package signing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
// SignRoot signs a root with a domain.
|
||||
func SignRoot(account e2wtypes.Account, root []byte, domain []byte) ([]byte, error) {
|
||||
// Ensure input is as expected.
|
||||
if account == nil {
|
||||
return nil, errors.New("account not specified")
|
||||
}
|
||||
if len(root) != 32 {
|
||||
return nil, errors.New("root must be 32 bytes in length")
|
||||
}
|
||||
if len(domain) != 32 {
|
||||
return nil, errors.New("domain must be 32 bytes in length")
|
||||
}
|
||||
|
||||
alreadyUnlocked, err := unlock(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var signature e2types.Signature
|
||||
// outputIf(debug, fmt.Sprintf("Signing %x (%d)", data, len(data)))
|
||||
if protectingSigner, isProtectingSigner := account.(e2wtypes.AccountProtectingSigner); isProtectingSigner {
|
||||
// Signer takes root and domain.
|
||||
signature, err = signProtected(protectingSigner, root[:], domain)
|
||||
} else if signer, isSigner := account.(e2wtypes.AccountSigner); isSigner {
|
||||
signature, err = sign(signer, root[:], domain)
|
||||
} else {
|
||||
return nil, errors.New("account does not provide signing facility")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !alreadyUnlocked {
|
||||
if err := lock(account); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to lock account")
|
||||
}
|
||||
}
|
||||
|
||||
return signature.Marshal(), nil
|
||||
}
|
||||
|
||||
func sign(account e2wtypes.AccountSigner, root []byte, domain []byte) (e2types.Signature, error) {
|
||||
container := &Container{
|
||||
Root: root,
|
||||
Domain: domain,
|
||||
}
|
||||
signingRoot, err := container.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to generate hash tree root")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
signature, err := account.Sign(ctx, signingRoot[:])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to sign")
|
||||
}
|
||||
|
||||
return signature, err
|
||||
}
|
||||
|
||||
func signProtected(account e2wtypes.AccountProtectingSigner, data []byte, domain []byte) (e2types.Signature, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
signature, err := account.SignGeneric(ctx, data, domain)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to sign")
|
||||
}
|
||||
|
||||
return signature, err
|
||||
}
|
||||
67
util/logging.go
Normal file
67
util/logging.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
zerologger "github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Log is the ethdo global logger.
|
||||
var Log zerolog.Logger
|
||||
|
||||
// initLogging initialises logging.
|
||||
func initLogging() error {
|
||||
// Change the output file.
|
||||
if viper.GetString("log-file") != "" {
|
||||
f, err := os.OpenFile(viper.GetString("log-file"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open log file")
|
||||
}
|
||||
zerologger.Logger = zerologger.Logger.Output(f)
|
||||
}
|
||||
|
||||
// Set the log level.
|
||||
Log = zerologger.Logger.With().Logger().Level(logLevel(viper.GetString("log-level")))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// logLevel converts a string to a log level.
|
||||
// It returns the user-supplied level by default.
|
||||
func logLevel(input string) zerolog.Level {
|
||||
switch strings.ToLower(input) {
|
||||
case "none":
|
||||
return zerolog.Disabled
|
||||
case "trace":
|
||||
return zerolog.TraceLevel
|
||||
case "debug":
|
||||
return zerolog.DebugLevel
|
||||
case "warn", "warning":
|
||||
return zerolog.WarnLevel
|
||||
case "info", "information":
|
||||
return zerolog.InfoLevel
|
||||
case "err", "error":
|
||||
return zerolog.ErrorLevel
|
||||
case "fatal":
|
||||
return zerolog.FatalLevel
|
||||
default:
|
||||
return Log.GetLevel()
|
||||
}
|
||||
}
|
||||
58
util/passphrases.go
Normal file
58
util/passphrases.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// GetStorePassphrases() fetches the store passphrase supplied by the user.
|
||||
func GetStorePassphrase() string {
|
||||
return viper.GetString("store-passphrase")
|
||||
}
|
||||
|
||||
// GetWalletPassphrases() fetches the wallet passphrase supplied by the user.
|
||||
func GetWalletPassphrase() string {
|
||||
return viper.GetString("wallet-passphrase")
|
||||
}
|
||||
|
||||
// GetPassphrases() fetches the passphrases supplied by the user.
|
||||
func GetPassphrases() []string {
|
||||
return viper.GetStringSlice("passphrase")
|
||||
}
|
||||
|
||||
// getPassphrase fetches the passphrase supplied by the user.
|
||||
func GetPassphrase() (string, error) {
|
||||
passphrases := GetPassphrases()
|
||||
if len(passphrases) == 0 {
|
||||
return "", errors.New("passphrase is required")
|
||||
}
|
||||
if len(passphrases) > 1 {
|
||||
return "", errors.New("multiple passphrases supplied")
|
||||
}
|
||||
return passphrases[0], nil
|
||||
}
|
||||
|
||||
// GetOptionalPassphrase fetches the passphrase if supplied by the user.
|
||||
func GetOptionalPassphrase() (string, error) {
|
||||
passphrases := GetPassphrases()
|
||||
if len(passphrases) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
if len(passphrases) > 1 {
|
||||
return "", errors.New("multiple passphrases supplied")
|
||||
}
|
||||
return passphrases[0], nil
|
||||
}
|
||||
162
util/passphrases_test.go
Normal file
162
util/passphrases_test.go
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
func TestGetStorePassphrase(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
passphrase string
|
||||
}{
|
||||
{
|
||||
name: "Good",
|
||||
passphrase: "pass",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
viper.Reset()
|
||||
viper.Set("store-passphrase", test.passphrase)
|
||||
require.Equal(t, test.passphrase, util.GetStorePassphrase())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWalletPassphrase(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
passphrase string
|
||||
}{
|
||||
{
|
||||
name: "Good",
|
||||
passphrase: "pass",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
viper.Reset()
|
||||
viper.Set("wallet-passphrase", test.passphrase)
|
||||
require.Equal(t, test.passphrase, util.GetWalletPassphrase())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPassphrases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
passphrases []string
|
||||
}{
|
||||
{
|
||||
name: "Single",
|
||||
passphrases: []string{"pass"},
|
||||
},
|
||||
{
|
||||
name: "Multi",
|
||||
passphrases: []string{"pass1", "pass2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
viper.Reset()
|
||||
if len(test.passphrases) == 1 {
|
||||
viper.Set("passphrase", test.passphrases[0])
|
||||
} else {
|
||||
viper.Set("passphrase", test.passphrases)
|
||||
}
|
||||
require.Equal(t, test.passphrases, util.GetPassphrases())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPassphrase(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
passphrases interface{}
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "None",
|
||||
err: "passphrase is required",
|
||||
},
|
||||
{
|
||||
name: "Single",
|
||||
passphrases: "pass",
|
||||
},
|
||||
{
|
||||
name: "Multi",
|
||||
passphrases: []string{"pass1", "pass2"},
|
||||
err: "multiple passphrases supplied",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
viper.Reset()
|
||||
viper.Set("passphrase", test.passphrases)
|
||||
res, err := util.GetPassphrase()
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.passphrases, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOptionalPassphrase(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
passphrases interface{}
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "None",
|
||||
passphrases: "",
|
||||
},
|
||||
{
|
||||
name: "Single",
|
||||
passphrases: "pass",
|
||||
},
|
||||
{
|
||||
name: "Multi",
|
||||
passphrases: []string{"pass1", "pass2"},
|
||||
err: "multiple passphrases supplied",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
viper.Reset()
|
||||
viper.Set("passphrase", test.passphrases)
|
||||
res, err := util.GetOptionalPassphrase()
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.passphrases, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user