wallet/recovery_test: adds test for 1-in-1-out spend
This commit is contained in:
parent
e4124d8e8b
commit
71ce1d5474
1 changed files with 245 additions and 0 deletions
245
wallet/recovery_test.go
Normal file
245
wallet/recovery_test.go
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
package wallet_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/roasbeef/btcwallet/wallet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Harness holds the BranchRecoveryState being tested, the recovery window being
|
||||||
|
// used, provides access to the test object, and tracks the expected horizon
|
||||||
|
// and next unfound values.
|
||||||
|
type Harness struct {
|
||||||
|
t *testing.T
|
||||||
|
brs *wallet.BranchRecoveryState
|
||||||
|
recoveryWindow uint32
|
||||||
|
expHorizon uint32
|
||||||
|
expNextUnfound uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Stepper is a generic interface that performs an action or assertion
|
||||||
|
// against a test Harness.
|
||||||
|
Stepper interface {
|
||||||
|
// Apply performs an action or assertion against branch recovery
|
||||||
|
// state held by the Harness. The step index is provided so
|
||||||
|
// that any failures can report which Step failed.
|
||||||
|
Apply(step int, harness *Harness)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitialiDelta is a Step that verifies our first attempt to expand the
|
||||||
|
// branch recovery state's horizons tells us to derive a number of
|
||||||
|
// adddresses equal to the recovery window.
|
||||||
|
InitialDelta struct{}
|
||||||
|
|
||||||
|
// CheckDelta is a Step that expands the branch recovery state's
|
||||||
|
// horizon, and checks that the returned delta meets our expected
|
||||||
|
// `delta`.
|
||||||
|
CheckDelta struct {
|
||||||
|
delta uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckNumInvalid is a Step that asserts that the branch recovery
|
||||||
|
// state reports `total` invalid children with the current horizon.
|
||||||
|
CheckNumInvalid struct {
|
||||||
|
total uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkInvalid is a Step that marks the `child` as invalid in the branch
|
||||||
|
// recovery state.
|
||||||
|
MarkInvalid struct {
|
||||||
|
child uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportFound is a Step that reports `child` as being found to the
|
||||||
|
// branch recovery state.
|
||||||
|
ReportFound struct {
|
||||||
|
child uint32
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Apply extends the current horizon of the branch recovery state, and checks
|
||||||
|
// that the returned delta is equal to the test's recovery window. If the
|
||||||
|
// assertions pass, the harness's expected horizon is increased by the returned
|
||||||
|
// delta.
|
||||||
|
//
|
||||||
|
// NOTE: This should be used before applying any CheckDelta steps.
|
||||||
|
func (_ InitialDelta) Apply(i int, h *Harness) {
|
||||||
|
curHorizon, delta := h.brs.ExtendHorizon()
|
||||||
|
assertHorizon(h.t, i, curHorizon, h.expHorizon)
|
||||||
|
assertDelta(h.t, i, delta, h.recoveryWindow)
|
||||||
|
h.expHorizon += delta
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply extends the current horizon of the branch recovery state, and checks
|
||||||
|
// that the returned delta is equal to the CheckDelta's child value.
|
||||||
|
func (d CheckDelta) Apply(i int, h *Harness) {
|
||||||
|
curHorizon, delta := h.brs.ExtendHorizon()
|
||||||
|
assertHorizon(h.t, i, curHorizon, h.expHorizon)
|
||||||
|
assertDelta(h.t, i, delta, d.delta)
|
||||||
|
h.expHorizon += delta
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply queries the branch recovery state for the number of invalid children
|
||||||
|
// that lie between the last found address and the current horizon, and compares
|
||||||
|
// that to the CheckNumInvalid's total.
|
||||||
|
func (m CheckNumInvalid) Apply(i int, h *Harness) {
|
||||||
|
assertNumInvalid(h.t, i, h.brs.NumInvalidInHorizon(), m.total)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply marks the MarkInvalid's child index as invalid in the branch recovery
|
||||||
|
// state, and increments the harness's expected horizon.
|
||||||
|
func (m MarkInvalid) Apply(i int, h *Harness) {
|
||||||
|
h.brs.MarkInvalidChild(m.child)
|
||||||
|
h.expHorizon++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply reports the ReportFound's child index as found in the branch recovery
|
||||||
|
// state. If the child index meets or exceeds our expected next unfound value,
|
||||||
|
// the expected value will be modified to be the child index + 1. Afterwards,
|
||||||
|
// this step asserts that the branch recovery state's next reported unfound
|
||||||
|
// value matches our potentially-updated value.
|
||||||
|
func (r ReportFound) Apply(i int, h *Harness) {
|
||||||
|
h.brs.ReportFound(r.child)
|
||||||
|
if r.child >= h.expNextUnfound {
|
||||||
|
h.expNextUnfound = r.child + 1
|
||||||
|
}
|
||||||
|
assertNextUnfound(h.t, i, h.brs.NextUnfound(), h.expNextUnfound)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time checks to ensure our steps implement the Step interface.
|
||||||
|
var _ Stepper = InitialDelta{}
|
||||||
|
var _ Stepper = CheckDelta{}
|
||||||
|
var _ Stepper = CheckNumInvalid{}
|
||||||
|
var _ Stepper = MarkInvalid{}
|
||||||
|
var _ Stepper = ReportFound{}
|
||||||
|
|
||||||
|
// TestBranchRecoveryState walks the BranchRecoveryState through a sequence of
|
||||||
|
// steps, verifying that:
|
||||||
|
// - the horizon is properly expanded in response to found addrs
|
||||||
|
// - report found children below or equal to previously found causes no change
|
||||||
|
// - marking invalid children expands the horizon
|
||||||
|
func TestBranchRecoveryState(t *testing.T) {
|
||||||
|
|
||||||
|
const recoveryWindow = 10
|
||||||
|
|
||||||
|
recoverySteps := []Stepper{
|
||||||
|
// First, check that expanding our horizon returns exactly the
|
||||||
|
// recovery window (10).
|
||||||
|
InitialDelta{},
|
||||||
|
|
||||||
|
// Expected horizon: 10.
|
||||||
|
|
||||||
|
// Report finding the 2nd addr, this should cause our horizon
|
||||||
|
// to expand by 2.
|
||||||
|
ReportFound{1},
|
||||||
|
CheckDelta{2},
|
||||||
|
|
||||||
|
// Expected horizon: 12.
|
||||||
|
|
||||||
|
// Sanity check that expanding again reports zero delta, as
|
||||||
|
// nothing has changed.
|
||||||
|
CheckDelta{0},
|
||||||
|
|
||||||
|
// Now, report finding the 6th addr, which should expand our
|
||||||
|
// horizon to 16 with a detla of 4.
|
||||||
|
ReportFound{5},
|
||||||
|
CheckDelta{4},
|
||||||
|
|
||||||
|
// Expected horizon: 16.
|
||||||
|
|
||||||
|
// Sanity check that expanding again reports zero delta, as
|
||||||
|
// nothing has changed.
|
||||||
|
CheckDelta{0},
|
||||||
|
|
||||||
|
// Report finding child index 5 again, nothing should change.
|
||||||
|
ReportFound{5},
|
||||||
|
CheckDelta{0},
|
||||||
|
|
||||||
|
// Report finding a lower index that what was last found,
|
||||||
|
// nothing should change.
|
||||||
|
ReportFound{4},
|
||||||
|
CheckDelta{0},
|
||||||
|
|
||||||
|
// Moving on, report finding the 11th addr, which should extend
|
||||||
|
// our horizon to 21.
|
||||||
|
ReportFound{10},
|
||||||
|
CheckDelta{5},
|
||||||
|
|
||||||
|
// Expected horizon: 21.
|
||||||
|
|
||||||
|
// Before testing the lookahead expansion when encountering
|
||||||
|
// invalid child keys, check that we are correctly starting with
|
||||||
|
// no invalid keys.
|
||||||
|
CheckNumInvalid{0},
|
||||||
|
|
||||||
|
// Now that the window has been expanded, simulate deriving
|
||||||
|
// invalid keys in range of addrs that are being derived for the
|
||||||
|
// first time. The horizon will be incremented by one, as the
|
||||||
|
// recovery manager is expected to try and derive at least the
|
||||||
|
// next address.
|
||||||
|
MarkInvalid{17},
|
||||||
|
CheckNumInvalid{1},
|
||||||
|
CheckDelta{0},
|
||||||
|
|
||||||
|
// Expected horizon: 22.
|
||||||
|
|
||||||
|
// Check that deriving a second invalid key shows both invalid
|
||||||
|
// indexes currently within the horizon.
|
||||||
|
MarkInvalid{18},
|
||||||
|
CheckNumInvalid{2},
|
||||||
|
CheckDelta{0},
|
||||||
|
|
||||||
|
// Expected horizon: 23.
|
||||||
|
|
||||||
|
// Lastly, report finding the addr immediately after our two
|
||||||
|
// invalid keys. This should return our number of invalid keys
|
||||||
|
// within the horizon back to 0.
|
||||||
|
ReportFound{19},
|
||||||
|
CheckNumInvalid{0},
|
||||||
|
|
||||||
|
// As the 20-th key was just marked found, our horizon will need
|
||||||
|
// to expand to 30. With the horizon at 23, the delta returned
|
||||||
|
// should be 7.
|
||||||
|
CheckDelta{7},
|
||||||
|
CheckDelta{0},
|
||||||
|
|
||||||
|
// Expected horizon: 30.
|
||||||
|
}
|
||||||
|
|
||||||
|
brs := wallet.NewBranchRecoveryState(recoveryWindow)
|
||||||
|
harness := &Harness{
|
||||||
|
t: t,
|
||||||
|
brs: brs,
|
||||||
|
recoveryWindow: recoveryWindow,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, step := range recoverySteps {
|
||||||
|
step.Apply(i, harness)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertHorizon(t *testing.T, i int, have, want uint32) {
|
||||||
|
assertHaveWant(t, i, "incorrect horizon", have, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertDelta(t *testing.T, i int, have, want uint32) {
|
||||||
|
assertHaveWant(t, i, "incorrect delta", have, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertNextUnfound(t *testing.T, i int, have, want uint32) {
|
||||||
|
assertHaveWant(t, i, "incorrect next unfound", have, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertNumInvalid(t *testing.T, i int, have, want uint32) {
|
||||||
|
assertHaveWant(t, i, "incorrect num invalid children", have, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertHaveWant(t *testing.T, i int, msg string, have, want uint32) {
|
||||||
|
_, _, line, _ := runtime.Caller(2)
|
||||||
|
if want != have {
|
||||||
|
t.Fatalf("[line: %d, step: %d] %s: got %d, want %d",
|
||||||
|
line, i, msg, have, want)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue