Initial cut of modular command structure

This commit is contained in:
Jim McDonald
2020-11-01 23:47:19 +00:00
parent 136e2fe9ba
commit 290ceb3f0d
24 changed files with 2512 additions and 187 deletions

View File

@@ -85,6 +85,8 @@ func persistentPreRun(cmd *cobra.Command, args []string) {
attesterInclusionBindings()
case "exit/verify":
exitVerifyBindings()
case "validator/depositdata":
validatorDepositdataBindings()
case "wallet/create":
walletCreateBindings()
}

View 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

View 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
}

View 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())
}
}
})
}
}

View 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
}

View 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)
}
})
}
}

View 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
}

View 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)
}
})
}
}

View 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
}

View 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"`
}

View 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
}

View File

@@ -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
View 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
View File

@@ -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
View File

@@ -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
View 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
View 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"`
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}
})
}
}