Skip to content

Commit a89275a

Browse files
authored
services/horizon: Check state rebuild in state verification integration test (#3127)
All integration tests start from empty state so we don't test building state from history archives. Extended state verification integration test to trigger state rebuild (`horizon expingest trigger-state-rebuild`) and verify rebuild state again. Such test would catch the bugs like #3100 and #3096.
1 parent cc9d6f0 commit a89275a

File tree

3 files changed

+116
-15
lines changed

3 files changed

+116
-15
lines changed

Diff for: .circleci/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ commands:
230230
command: |
231231
cd ~/go/src/github.com/stellar/go
232232
docker pull stellar/quickstart:testing
233-
<<# parameters.enable-captive-core >>HORIZON_INTEGRATION_ENABLE_CAPTIVE_CORE=true<</ parameters.enable-captive-core >> go test -timeout 20m -v ./services/horizon/internal/integration/...
233+
<<# parameters.enable-captive-core >>HORIZON_INTEGRATION_ENABLE_CAPTIVE_CORE=true<</ parameters.enable-captive-core >> go test -timeout 25m -v ./services/horizon/internal/integration/...
234234
235235
#-----------------------------------------------------------------------------#
236236
# Jobs use the commands to accomplish a given task, and run through workflows #

Diff for: services/horizon/internal/integration/protocol14_state_verifier_test.go

+48-11
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ import (
1414
"github.com/stretchr/testify/assert"
1515
)
1616

17+
const (
18+
firstCheckpoint = (64 * (iota + 1)) - 1
19+
secondCheckpoint
20+
thirdCheckpoint
21+
)
22+
1723
func TestProtocol14StateVerifier(t *testing.T) {
1824
itest := integration.NewTest(t, protocol15Config)
1925

@@ -99,36 +105,67 @@ func TestProtocol14StateVerifier(t *testing.T) {
99105
assert.True(t, txResp.Successful)
100106

101107
// Reach the first checkpoint ledger
102-
for !itest.LedgerIngested(63) {
103-
err := itest.CloseCoreLedger()
104-
assert.NoError(t, err)
105-
time.Sleep(50 * time.Millisecond)
108+
// Core will push to history archives *after* checkpoint ledger
109+
itest.CloseCoreLedgersUntilSequence(firstCheckpoint + 1)
110+
assert.NoError(t, err)
111+
for !itest.LedgerIngested(firstCheckpoint) {
112+
time.Sleep(time.Second)
113+
}
114+
115+
verified := waitForStateVerifications(itest, 1)
116+
if !verified {
117+
t.Fatal("State verification not run...")
118+
}
119+
120+
// Trigger state rebuild to check if ingesting from history archive works
121+
itest.RunHorizonCLICommand("expingest", "trigger-state-rebuild")
122+
123+
// Wait for the second checkpoint ledger and state rebuild
124+
// Core will push to history archives *after* checkpoint ledger
125+
itest.CloseCoreLedgersUntilSequence(secondCheckpoint + 1)
126+
assert.NoError(t, err)
127+
128+
// Wait for the third checkpoint ledger and state verification trigger
129+
// Core will push to history archives *after* checkpoint ledger
130+
itest.CloseCoreLedgersUntilSequence(thirdCheckpoint + 1)
131+
assert.NoError(t, err)
132+
for !itest.LedgerIngested(thirdCheckpoint) {
133+
time.Sleep(time.Second)
106134
}
107135

108-
var metrics string
136+
verified = waitForStateVerifications(itest, 2)
137+
if !verified {
138+
t.Fatal("State verification not run...")
139+
}
140+
}
109141

142+
func waitForStateVerifications(itest *integration.Test, count int) bool {
143+
t := itest.CurrentTest()
110144
// Check metrics until state verification run
111-
for i := 0; i < 60; i++ {
145+
for i := 0; i < 120; i++ {
112146
t.Logf("Checking metrics (%d attempt)\n", i)
113-
res, err := http.Get(fmt.Sprintf("http://localhost:%d/metrics", itest.AdminPort()))
147+
res, err := http.Get(itest.MetricsURL())
114148
assert.NoError(t, err)
115149

116150
metricsBytes, err := ioutil.ReadAll(res.Body)
117151
res.Body.Close()
118152
assert.NoError(t, err)
119-
metrics = string(metricsBytes)
153+
metrics := string(metricsBytes)
120154

121155
stateInvalid := strings.Contains(metrics, "horizon_ingest_state_invalid 1")
122156
assert.False(t, stateInvalid, "State is invalid!")
123157

124-
notVerifiedYet := strings.Contains(metrics, "horizon_ingest_state_verify_duration_seconds_count 0")
158+
notVerifiedYet := strings.Contains(
159+
metrics,
160+
fmt.Sprintf("horizon_ingest_state_verify_duration_seconds_count %d", count-1),
161+
)
125162
if notVerifiedYet {
126163
time.Sleep(time.Second)
127164
continue
128165
}
129166

130-
return
167+
return true
131168
}
132169

133-
t.Fatal("State verification not run...")
170+
return false
134171
}

Diff for: services/horizon/internal/test/integration/integration.go

+67-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//lint:file-ignore U1001 Ignore all unused code, this is only used in tests.
12
package integration
23

34
import (
@@ -333,18 +334,33 @@ func (i *Test) Client() *sdk.Client {
333334
// ingested by Horizon. Panics in case of errors.
334335
func (i *Test) LedgerIngested(sequence uint32) bool {
335336
root, err := i.Client().Root()
336-
if err != nil {
337-
panic(err)
338-
}
337+
panicIf(err)
339338

340339
return root.IngestSequence >= sequence
341340
}
342341

342+
// LedgerClosed returns true if the ledger with a given sequence has been
343+
// closed by Stellar-Core. Panics in case of errors. Note it's different
344+
// than LedgerIngested because it checks if the ledger was closed, not
345+
// necessarily ingested (ex. when rebuilding state Horizon does not ingest
346+
// recent ledgers).
347+
func (i *Test) LedgerClosed(sequence uint32) bool {
348+
root, err := i.Client().Root()
349+
panicIf(err)
350+
351+
return root.CoreSequence >= int32(sequence)
352+
}
353+
343354
// AdminPort returns Horizon admin port.
344355
func (i *Test) AdminPort() int {
345356
return 6060
346357
}
347358

359+
// Metrics URL returns Horizon metrics URL.
360+
func (i *Test) MetricsURL() string {
361+
return fmt.Sprintf("http://localhost:%d/metrics", i.AdminPort())
362+
}
363+
348364
// Master returns a keypair of the network master account.
349365
func (i *Test) Master() *keypair.Full {
350366
return keypair.Master(NetworkPassphrase).(*keypair.Full)
@@ -638,9 +654,43 @@ func (i *Test) CreateSignedTransaction(
638654
return tx, nil
639655
}
640656

657+
// CloseCoreLedgersUntilSequence will close ledgers until sequence.
658+
// Note: because manualclose command doesn't block until ledger is actually
659+
// closed, after running this method the last sequence can be higher than seq.
660+
func (i *Test) CloseCoreLedgersUntilSequence(seq int) error {
661+
for {
662+
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
663+
defer cancel()
664+
info, err := i.cclient.Info(ctx)
665+
if err != nil {
666+
return err
667+
}
668+
669+
if info.Info.Ledger.Num >= seq {
670+
return nil
671+
}
672+
673+
i.t.Logf(
674+
"Currently at ledger: %d, want: %d.",
675+
info.Info.Ledger.Num,
676+
seq,
677+
)
678+
679+
err = i.CloseCoreLedger()
680+
if err != nil {
681+
return err
682+
}
683+
// manualclose command in core doesn't block until ledger is actually
684+
// closed. Let's give it time to close the ledger.
685+
time.Sleep(200 * time.Millisecond)
686+
}
687+
}
688+
689+
// CloseCoreLedgers will close one ledger.
641690
func (i *Test) CloseCoreLedger() error {
642691
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
643692
defer cancel()
693+
i.t.Log("Closing one ledger manually...")
644694
return i.cclient.ManualClose(ctx)
645695
}
646696

@@ -686,6 +736,20 @@ func (i *Test) LogFailedTx(txResponse proto.Transaction, horizonResult error) {
686736
"Transaction doesn't have success code.")
687737
}
688738

739+
func (i *Test) RunHorizonCLICommand(cmd ...string) {
740+
fullCmd := append([]string{"/stellar/horizon/bin/horizon"}, cmd...)
741+
id, err := i.cli.ContainerExecCreate(
742+
context.Background(),
743+
i.container.ID,
744+
types.ExecConfig{
745+
Cmd: fullCmd,
746+
},
747+
)
748+
panicIf(err)
749+
err = i.cli.ContainerExecStart(context.Background(), id.ID, types.ExecStartCheck{})
750+
panicIf(err)
751+
}
752+
689753
// Cluttering code with if err != nil is absolute nonsense.
690754
func panicIf(err error) {
691755
if err != nil {

0 commit comments

Comments
 (0)