summaryrefslogtreecommitdiff
path: root/test/framework/fixtures/kmdFixture.go
blob: 21c70dd316b9ff040883cb52dd218d16d3052f72 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Copyright (C) 2019-2023 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand.  If not, see <https://www.gnu.org/licenses/>.

package fixtures

import (
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/algorand/go-algorand/daemon/kmd/client"
	"github.com/algorand/go-algorand/daemon/kmd/config"
	"github.com/algorand/go-algorand/daemon/kmd/lib/kmdapi"
	"github.com/algorand/go-algorand/nodecontrol"
	"github.com/algorand/go-algorand/util"
)

// defaultConfig lowers scrypt params to make tests faster
var defaultConfig = `{"drivers":{"sqlite":{"scrypt":{"scrypt_n":2},"allow_unsafe_scrypt":true}}}`

// shutdownTimeoutSecs is time to wait for kmd to shut down before returning an error
const shutdownTimeoutSecs = 5

// defaultTimeoutSecs is the number of seconds after which kmd will die if it
// receives no requests
const defaultTimeoutSecs = 60

var defaultWalletName = "default"
var defaultWalletPassword = "hunter2"
var defaultWalletDriver = "sqlite"
var defaultAPIToken = []byte(strings.Repeat("a", 64))

// KMDFixture is a test fixture for tests requiring interactions with kmd
type KMDFixture struct {
	baseFixture
	t              TestingTB
	initialized    bool
	dataDir        string
	kmdDir         string
	Sock           string
	APIToken       []byte
	WalletName     string
	WalletPassword string
	Client         *client.KMDClient
}

// Run runs all of the tests for this fixture
func (f *KMDFixture) Run(m *testing.M) int {
	return f.run(m)
}

// RunAndExit is like Run, but then calls ShutdownImpl
func (f *KMDFixture) RunAndExit(m *testing.M) {
	f.runAndExit(m)
}

// Shutdown stops the kmd instance if it's running and cleans up the dataDir if
// there was no test failure
func (f *KMDFixture) Shutdown() {
	// If there's a kmd server running
	if f.initialized {
		nc := nodecontrol.MakeNodeController(f.binDir, f.dataDir)
		nc.SetKMDDataDir(f.kmdDir)
		_, err := nc.StopKMD()
		require.NoError(f.t, err)
	}

	// Clean up test folder if there was no failure
	if !f.t.Failed() {
		os.RemoveAll(f.dataDir)
	}
}

// ShutdownImpl is not relevant for kmd so just panics
func (f *KMDFixture) ShutdownImpl(preserveData bool) {
	panic("ShutdownImpl not supported for *KMDFixture")
}

// SetupWithWallet starts kmd and creates a wallet, returning a wallet handle
func (f *KMDFixture) SetupWithWallet(t TestingTB) (handleToken string) {
	f.Setup(t)
	handleToken, _ = f.MakeWalletAndHandleToken()
	return
}

// Setup starts kmd with the default config
func (f *KMDFixture) Setup(t TestingTB) {
	f.SetupWithConfig(t, "")
}

// Initialize initializes the dataDir and TestingT for this test but doesn't start kmd
func (f *KMDFixture) Initialize(t TestingTB) {
	f.initialize(f)
	f.t = SynchronizedTest(t)
	f.dataDir = filepath.Join(f.testDir, t.Name())
	// Remove any existing tests in this dataDir + recreate
	err := os.RemoveAll(f.dataDir)
	require.NoError(f.t, err)
	err = os.Mkdir(f.dataDir, 0750)
	require.NoError(f.t, err)

	// Set up the kmd data dir within the main datadir
	f.kmdDir = filepath.Join(f.dataDir, nodecontrol.DefaultKMDDataDir)
	err = os.Mkdir(f.kmdDir, nodecontrol.DefaultKMDDataDirPerms)
	require.NoError(f.t, err)
}

// SetupWithConfig starts a kmd node with the passed config or default test
// config, if the passed config is blank. Though internally an error might
// occur during setup, we never return one, because we'll still fail the test
// for any errors here, and it keeps the test code much cleaner
func (f *KMDFixture) SetupWithConfig(t TestingTB, config string) {
	// Setup is called once per test, so it's OK for test to store one particular TestingT
	f.Initialize(t)

	// Write a token
	f.APIToken = defaultAPIToken
	tokenFilepath := filepath.Join(f.kmdDir, "kmd.token")
	err := os.WriteFile(tokenFilepath, f.APIToken, 0640)
	require.NoError(f.t, err)

	if config == "" {
		config = defaultConfig
	}
	configFilepath := filepath.Join(f.kmdDir, "kmd_config.json")
	err = os.WriteFile(configFilepath, []byte(config), 0640)
	require.NoError(f.t, err)

	// Start kmd
	nc := nodecontrol.MakeNodeController(f.binDir, f.dataDir)
	nc.SetKMDDataDir(f.kmdDir)
	_, err = nc.StartKMD(nodecontrol.KMDStartArgs{
		TimeoutSecs: defaultTimeoutSecs,
	})
	require.NoError(f.t, err)

	// Mark ourselves as initialized so we know to shut down server
	f.initialized = true

	// Build a client
	sock, err := util.GetFirstLineFromFile(filepath.Join(f.kmdDir, "kmd.net"))
	require.NoError(f.t, err)
	f.Sock = sock
	client, err := client.MakeKMDClient(f.Sock, string(f.APIToken))
	require.NoError(f.t, err)
	f.Client = &client
}

// MakeWalletAndHandleToken creates a wallet and returns a wallet handle to it
func (f *KMDFixture) MakeWalletAndHandleToken() (handleToken string, err error) {
	// Create a wallet
	req0 := kmdapi.APIV1POSTWalletRequest{
		WalletName:       defaultWalletName,
		WalletPassword:   defaultWalletPassword,
		WalletDriverName: defaultWalletDriver,
	}

	// We only ever associate one wallet with a fixture
	f.WalletPassword = defaultWalletPassword
	f.WalletName = defaultWalletName

	resp0 := kmdapi.APIV1POSTWalletResponse{}
	err = f.Client.DoV1Request(req0, &resp0)
	require.NoError(f.t, err)

	// Get a wallet token
	req1 := kmdapi.APIV1POSTWalletInitRequest{
		WalletID:       resp0.Wallet.ID,
		WalletPassword: defaultWalletPassword,
	}
	resp1 := kmdapi.APIV1POSTWalletInitResponse{}
	err = f.Client.DoV1Request(req1, &resp1)
	require.NoError(f.t, err)

	// Return the token
	return resp1.WalletHandleToken, nil
}

// TestConfig checks whether or not the passed config would be considered valid
func (f *KMDFixture) TestConfig(cfg []byte) error {
	// Write the passed config
	configFilepath := filepath.Join(f.kmdDir, "kmd_config.json")
	err := os.WriteFile(configFilepath, cfg, 0640)
	if err != nil {
		return err
	}
	// Check it with the config package
	_, err = config.LoadKMDConfig(f.kmdDir)
	return err
}