Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing liquidation value #65

Merged
merged 10 commits into from
Jun 28, 2024
23 changes: 23 additions & 0 deletions .github/workflows/daily_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Go Daily Termination Tests

on:
schedule:
- cron: '0 0 * * *'

jobs:
test:
name: Run Go Tests
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19.1

- name: Test
run: go test -v ./terminate
env:
LOTUS_DIAL_ADDR: ${{ secrets.LOTUS_DIAL_ADDR }}
LOTUS_TOKEN: ${{ secrets.LOTUS_TOKEN }}
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,9 @@ type PoolsSDK interface {
```

### Generating mocks
We use [mockery](https://vektra.github.io/mockery/#why-mockery) for generating mock files, which are output to the `./mock` package. For example, see `types/types.go` and run `go generate types/types.go`
We use [mockery](https://vektra.github.io/mockery/#why-mockery) for generating mock files, which are output to the `./mock` package. For example, see `types/types.go` and run `go generate types/types.go`

### Testing
To run certain tests, a couple environment variables must be exported:
`LOTUS_DIAL_ADDRESS`<br />
`LOTUS_TOKEN`
9 changes: 2 additions & 7 deletions econ/pmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/builtin"
"github.com/glifio/go-pools/abigen"
"github.com/glifio/go-pools/util"
)

func TestInterestOwed(t *testing.T) {
Expand All @@ -35,13 +36,7 @@ func TestInterestOwed(t *testing.T) {

owed := InterestOwed(context.Background(), testAccount, rate, currentEpoch)

if !assertApproxEqAbs(owed, expectedOwed, DUST) {
if !util.AssertApproxEqAbs(owed, expectedOwed, DUST) {
t.Errorf("expected %v, got %v", expectedOwed, owed)
}
}

func assertApproxEqAbs(a, b, DUST *big.Int) bool {
diff := new(big.Int).Sub(a, b)
diff.Abs(diff)
return diff.Cmp(DUST) <= 0
}
57 changes: 32 additions & 25 deletions terminate/preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,16 @@ func PreviewTerminateSectors(
gasLimit = 90000000000 * 3 // 3 deadlines per batch
}

progressCh <- &PreviewTerminateSectorsProgress{
Epoch: h,
MinerInfo: minerInfo,
WorkerActor: workerActor,
PrevHeightForImmutable: prevHeight,
WorkerActorPrev: workerActorPrev,
BatchSize: batchSize,
GasLimit: gasLimit,
if progressCh != nil {
progressCh <- &PreviewTerminateSectorsProgress{
Epoch: h,
MinerInfo: minerInfo,
WorkerActor: workerActor,
PrevHeightForImmutable: prevHeight,
WorkerActorPrev: workerActorPrev,
BatchSize: batchSize,
GasLimit: gasLimit,
}
}

if autoBatchSize && batchSize < 10 {
Expand Down Expand Up @@ -320,13 +322,16 @@ func PreviewTerminateSectors(
errorCh <- err
return
}
progressCh <- &PreviewTerminateSectorsProgress{
DeadlinePartitionCount: len(deadlinePartitions),
DeadlinePartitionIndex: deadlinePartitionIdx,
Deadline: dlIdx,
DeadlineImmutable: dlImmutable,
Partition: partIdx,
SectorsCount: sc,

if progressCh != nil {
progressCh <- &PreviewTerminateSectorsProgress{
DeadlinePartitionCount: len(deadlinePartitions),
DeadlinePartitionIndex: deadlinePartitionIdx,
Deadline: dlIdx,
DeadlineImmutable: dlImmutable,
Partition: partIdx,
SectorsCount: sc,
}
}
if sc > 0 {

Expand All @@ -346,16 +351,18 @@ func PreviewTerminateSectors(
return
}

progressCh <- &PreviewTerminateSectorsProgress{
DeadlinePartitionCount: len(deadlinePartitions),
DeadlinePartitionIndex: deadlinePartitionIdx,
Deadline: dlIdx,
DeadlineImmutable: dlImmutable,
Partition: partIdx,
SectorsCount: sc,
SliceStart: i,
SliceEnd: lastIndex,
SliceCount: sliceCount,
if progressCh != nil {
progressCh <- &PreviewTerminateSectorsProgress{
DeadlinePartitionCount: len(deadlinePartitions),
DeadlinePartitionIndex: deadlinePartitionIdx,
Deadline: dlIdx,
DeadlineImmutable: dlImmutable,
Partition: partIdx,
SectorsCount: sc,
SliceStart: i,
SliceEnd: lastIndex,
SliceCount: sliceCount,
}
}

termination := miner.TerminationDeclaration{
Expand Down
97 changes: 97 additions & 0 deletions terminate/terminate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package terminate

import (
"context"
"fmt"
"log"
"math/big"
"testing"

"github.com/filecoin-project/lotus/chain/types"
"github.com/glifio/go-pools/util"
)

// this test compares N random miner termination penalties computed in the most precise (time intensive) way against the quick, less precise sampling method used in the ADO
var N = 10

// the wad based percentage that is acceptible for imprecision
var DIFF = big.NewInt(3e16)

func TestTerminationPrecision(t *testing.T) {
lapi, closer := util.SetupSuite(t)
defer util.TeardownSuite(closer)

// get ChainHead and lookback 5 epochs
head, err := lapi.ChainHead(context.Background())
if err != nil {
t.Fatal(err)
}
// TODO: this doesn't align with `@head` passed to the PreviewTerminateSectors call
lookback := head.Height() - 1
ts, err := lapi.ChainGetTipSetByHeight(context.Background(), lookback, types.EmptyTSK)
if err != nil {
t.Fatal(err)
}

miners, err := lapi.StateListMiners(context.Background(), ts.Key())
if err != nil {
t.Fatal(err)
}

// // make a slice the length of N
// chosenMiners := make([]address.Address, N)

// for i := 0; i < N; i++ {
// // choose a random miner
// randomIndex := rand.Intn(len(miners))
// chosenMiners[i] = miners[randomIndex]
// }
// eventually run this test on all 10 miners in parallel
idx := 0
miner := miners[0]
hasBalance := false
// check if the miner has a balance
for !hasBalance {
sectorCount, err := lapi.StateMinerSectorCount(context.Background(), miner, ts.Key())
if err != nil {
t.Fatal(err)
}
// if the miner has no active sectors, move to the next miner
fmt.Println(miner, sectorCount.Active)
if sectorCount.Active == 0 {
idx++
miner = miners[idx]
} else {
hasBalance = true
}
}

imprecise, err := PreviewTerminateSectorsQuick(context.Background(), lapi, miner, ts)
if err != nil {
t.Fatal(err)
}

errorCh := make(chan error)
resultCh := make(chan *PreviewTerminateSectorsReturn)

go PreviewTerminateSectors(context.Background(), lapi, miner, "@head", 0, 0, 0, false, false, false, 0, errorCh, nil, resultCh)

loop:
for {
select {
case result := <-resultCh:
fmt.Println("PRECISE: ", result.SectorStats.TerminationPenalty)
fmt.Println("IMPRECISE: ", imprecise.SectorStats.TerminationPenalty)
if !util.AssertApproxEqRel(result.SectorStats.TerminationPenalty, imprecise.SectorStats.TerminationPenalty, DIFF) {
t.Fatalf("TERMINATION PENALTIES DO NOT MATCH: precise: %v, imprecise: %v", result.SectorStats.TerminationPenalty, imprecise.SectorStats.TerminationPenalty)
}
break loop

case err := <-errorCh:
log.Fatal(err)
}

}

// TODO: test the 10 random miners instead of just 1
}
39 changes: 31 additions & 8 deletions util/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,34 @@ package util

import (
"context"
"math/big"
"net/http"
"os"
"testing"

"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
)

var DIAL_ADDR = ""
var TOKEN = ""

func SetupSuite(t *testing.T) (*api.FullNodeStruct, jsonrpc.ClientCloser) {
if DIAL_ADDR == "" {
t.Fatal("DIAL_ADDR must be set")
lotusDialAddr := os.Getenv("LOTUS_DIAL_ADDR")
lotusToken := os.Getenv("LOTUS_TOKEN")

if lotusDialAddr == "" {
t.Fatal("LOTUS_DIAL_ADDR env var must be set")
}

var lcli api.FullNodeStruct = api.FullNodeStruct{}
head := http.Header{}

if TOKEN != "" {
head.Add("Authorization", "Bearer "+TOKEN)
if lotusToken != "" {
head.Add("Authorization", "Bearer "+lotusToken)
}

closer, err := jsonrpc.NewMergeClient(
context.Background(),
DIAL_ADDR,
lotusDialAddr,
"Filecoin",
api.GetInternalStructs(&lcli),
head,
Expand All @@ -51,3 +53,24 @@ func SetupSuite(t *testing.T) (*api.FullNodeStruct, jsonrpc.ClientCloser) {
func TeardownSuite(close jsonrpc.ClientCloser) {
defer close()
}

func AssertApproxEqAbs(a, b, DUST *big.Int) bool {
diff := new(big.Int).Sub(a, b)
diff.Abs(diff)
return diff.Cmp(DUST) <= 0
}

// DIFF is a WAD math based percentage, such that 1e18 is 100%
func AssertApproxEqRel(a, b, DIFF *big.Int) bool {
// compute the diff
diff := new(big.Int).Sub(a, b)
diff.Abs(diff)

// Calculate the difference in terms of percentage: (|a - b| / a) * 1e18
// To avoid losing precision, first multiply diff by 1e18, then divide by a
percentageDiff := new(big.Int).Mul(diff, big.NewInt(1e18))
percentageDiff.Div(percentageDiff, a)

// Check if the calculated percentage difference is within the specified range
return percentageDiff.Cmp(DIFF) <= 0
}
52 changes: 52 additions & 0 deletions util/tests_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package util

import (
"math/big"
"testing"
)

func TestApproxEqualRel(t *testing.T) {
testCases := []struct {
name string
a, b, rangeWAD *big.Int
want bool
}{
{
name: "Equal values",
a: big.NewInt(1e18), // 1 WAD
b: big.NewInt(1e18), // 1 WAD
rangeWAD: big.NewInt(1e17), // 10%
want: true,
},
{
name: "Within range",
a: big.NewInt(1e18), // 1 WAD
b: big.NewInt(1e18 + 5e16), // 1.05 WAD
rangeWAD: big.NewInt(1e17), // 10%
want: true,
},
{
name: "Outside range",
a: big.NewInt(1e18), // 1 WAD
b: big.NewInt(1e18 + 2e17), // 1.2 WAD
rangeWAD: big.NewInt(1e17), // 10%
want: false,
},
{
name: "A is zero",
a: big.NewInt(0), // 0 WAD
b: big.NewInt(1e18), // 1 WAD
rangeWAD: big.NewInt(1e17), // 10%
want: false, // Cannot compare if 'a' is zero
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := AssertApproxEqAbs(tc.a, tc.b, tc.rangeWAD)
if got != tc.want {
t.Errorf("approximatelyEqual(%v, %v, %v) = %v; want %v", tc.a, tc.b, tc.rangeWAD, got, tc.want)
}
})
}
}