2018-04-26 10:47:56 +02:00
|
|
|
package wallet_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"runtime"
|
|
|
|
"testing"
|
|
|
|
|
2018-05-15 07:11:11 +02:00
|
|
|
"github.com/btcsuite/btcwallet/wallet"
|
2018-04-26 10:47:56 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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.
|
2021-03-24 14:43:24 +01:00
|
|
|
func (InitialDelta) Apply(i int, h *Harness) {
|
2018-04-26 10:47:56 +02:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|