Skip to content

Commit

Permalink
Merge pull request #154 from SiaFoundation/nate/maturity-off-by-one
Browse files Browse the repository at this point in the history
Fix event maturity height
  • Loading branch information
n8maninger authored Jul 16, 2024
2 parents d66a7dc + 3bb27e9 commit 6f03596
Show file tree
Hide file tree
Showing 8 changed files with 897 additions and 93 deletions.
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ toolchain go1.22.3

require (
github.com/mattn/go-sqlite3 v1.14.22
go.sia.tech/core v0.3.0
go.sia.tech/coreutils v0.1.0
go.sia.tech/core v0.4.0
go.sia.tech/coreutils v0.2.0
go.sia.tech/jape v0.12.0
go.sia.tech/web/walletd v0.22.3
go.uber.org/zap v1.27.0
Expand All @@ -25,7 +25,7 @@ require (
go.sia.tech/mux v1.2.0 // indirect
go.sia.tech/web v0.0.0-20240610131903-5611d44a533e // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/tools v0.22.0 // indirect
)
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
go.sia.tech/core v0.3.0 h1:PDfAQh9z8PYD+oeVS7rS9SEnTMOZzwwFfAH45yktmko=
go.sia.tech/core v0.3.0/go.mod h1:BMgT/reXtgv6XbDgUYTCPY7wSMbspDRDs7KMi1vL6Iw=
go.sia.tech/coreutils v0.1.0 h1:WQL7iT+jK1BiMx87bASXrZJZf4N2fbQkIOW8rS7wkh4=
go.sia.tech/coreutils v0.1.0/go.mod h1:ybaFgewKXrlxFW71LqsyQlxjG6yWL6BSePrbZYnrprU=
go.sia.tech/core v0.4.0 h1:TlbVuiw1nk7wAybSvuZozRixnI4lpmcK0MVIlpJ9ApA=
go.sia.tech/core v0.4.0/go.mod h1:6dN3J2GDX+f8H2p82MJ7V4BFdnmgoHAiovfmBD/F1Hg=
go.sia.tech/coreutils v0.2.0 h1:Tad3SPPyUM0gW/jwCxMFFgOa6YSkcwH0dsUv6muB4NA=
go.sia.tech/coreutils v0.2.0/go.mod h1:WpdAhWmtQ8gyqJfXnHhWLsnWn+j1eDkkWuvFVMDG4IU=
go.sia.tech/jape v0.12.0 h1:13fBi7c5X8zxTQ05Cd9ZsIfRJgdvGoZqbEzH861z7BU=
go.sia.tech/jape v0.12.0/go.mod h1:wU+h6Wh5olDjkPXjF0tbZ1GDgoZ6VTi4naFw91yyWC4=
go.sia.tech/mux v1.2.0 h1:ofa1Us9mdymBbGMY2XH/lSpY8itFsKIo/Aq8zwe+GHU=
Expand All @@ -30,8 +30,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
Expand Down
27 changes: 18 additions & 9 deletions persist/sqlite/peers.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,16 +207,25 @@ func (s *Store) Banned(peer string) (banned bool, _ error) {
}

err = s.transaction(func(tx *txn) error {
query := `SELECT net_cidr, expiration FROM syncer_bans WHERE net_cidr IN (` + queryPlaceHolders(len(checkSubnets)) + `) ORDER BY expiration DESC LIMIT 1`

var subnet string
var expiration time.Time
err := tx.QueryRow(query, queryArgs(checkSubnets)...).Scan(&subnet, decode(&expiration))
banned = time.Now().Before(expiration) // will return false for any sql errors, including ErrNoRows
if err == nil && banned {
s.log.Debug("found ban", zap.String("subnet", subnet), zap.Time("expiration", expiration))
checkSubnetStmt, err := tx.Prepare(`SELECT expiration FROM syncer_bans WHERE net_cidr = $1 ORDER BY expiration DESC LIMIT 1`)
if err != nil {
return fmt.Errorf("failed to prepare statement: %w", err)
}
return err
defer checkSubnetStmt.Close()

for _, subnet := range checkSubnets {
var expiration time.Time

err := checkSubnetStmt.QueryRow(subnet).Scan(decode(&expiration))
banned = time.Now().Before(expiration) // will return false for any sql errors, including ErrNoRows
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("failed to check ban status: %w", err)
} else if banned {
s.log.Debug("found ban", zap.String("subnet", subnet), zap.Time("expiration", expiration))
return nil
}
}
return nil
})
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return false, fmt.Errorf("failed to check ban status: %w", err)
Expand Down
27 changes: 0 additions & 27 deletions persist/sqlite/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"math/rand"
"strings"
"time"

_ "github.com/mattn/go-sqlite3" // import sqlite3 driver
Expand Down Expand Up @@ -171,32 +170,6 @@ func (tx *txn) QueryRow(query string, args ...any) *row {
return &row{r, tx.log.Named("row")}
}

func queryPlaceHolders(n int) string {
if n == 0 {
return ""
} else if n == 1 {
return "?"
}
var b strings.Builder
b.Grow(((n - 1) * 2) + 1) // ?,?
for i := 0; i < n-1; i++ {
b.WriteString("?,")
}
b.WriteString("?")
return b.String()
}

func queryArgs[T any](args []T) []any {
if len(args) == 0 {
return nil
}
out := make([]any, len(args))
for i, arg := range args {
out[i] = arg
}
return out
}

// getDBVersion returns the current version of the database.
func getDBVersion(db *sql.DB) (version int64) {
// error is ignored -- the database may not have been initialized yet.
Expand Down
43 changes: 29 additions & 14 deletions persist/sqlite/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,42 @@ import (
)

func (s *Store) getWalletEventRelevantAddresses(tx *txn, id wallet.ID, eventIDs []int64) (map[int64][]types.Address, error) {
query := `SELECT ea.event_id, sa.sia_address
stmt, err := tx.Prepare(`SELECT sa.sia_address
FROM event_addresses ea
INNER JOIN sia_addresses sa ON (ea.address_id = sa.id)
WHERE event_id IN (` + queryPlaceHolders(len(eventIDs)) + `) AND address_id IN (SELECT address_id FROM wallet_addresses WHERE wallet_id=?)`

rows, err := tx.Query(query, append(queryArgs(eventIDs), id)...)
INNER JOIN wallet_addresses wa ON (ea.address_id = wa.address_id)
WHERE wa.wallet_id=? AND ea.event_id=?`)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to prepare statement: %w", err)
}
defer stmt.Close()

relevant := func(walletID wallet.ID, eventID int64) (addresses []types.Address, err error) {
rows, err := stmt.Query(walletID, eventID)
if err != nil {
return nil, fmt.Errorf("failed to query relevant addresses: %w", err)
}
defer rows.Close()

for rows.Next() {
var address types.Address
if err := rows.Scan(decode(&address)); err != nil {
return nil, fmt.Errorf("failed to scan relevant address: %w", err)
}
addresses = append(addresses, address)
}
return addresses, rows.Err()
}
defer rows.Close()

relevantAddresses := make(map[int64][]types.Address)
for rows.Next() {
var eventID int64
var address types.Address
if err := rows.Scan(&eventID, decode(&address)); err != nil {
return nil, fmt.Errorf("failed to scan relevant address: %w", err)
for _, eventID := range eventIDs {
addresses, err := relevant(id, eventID)
if err != nil {
return nil, err
}
relevantAddresses[eventID] = append(relevantAddresses[eventID], address)
relevantAddresses[eventID] = addresses
}
return relevantAddresses, rows.Err()
return relevantAddresses, nil
}

// WalletEvents returns the events relevant to a wallet, sorted by height descending.
Expand Down Expand Up @@ -657,7 +672,7 @@ func getWalletEvents(tx *txn, id wallet.ID, offset, limit int) (events []wallet.
}

const eventsQuery = `SELECT ev.id, ev.event_id, ev.maturity_height, ev.date_created, ci.height, ci.block_id, ev.event_type, ev.event_data
FROM events ev INDEXED BY events_maturity_height_id_idx -- force the index to prevent temp-btree sorts
FROM events ev
INNER JOIN event_addresses ea ON (ev.id = ea.event_id)
INNER JOIN wallet_addresses wa ON (ea.address_id = wa.address_id)
INNER JOIN chain_indices ci ON (ev.chain_index_id = ci.id)
Expand Down
6 changes: 3 additions & 3 deletions wallet/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ type (
// An EventV2ContractResolution represents a file contract payout from a v2
// contract.
EventV2ContractResolution struct {
types.V2FileContractResolution
SiacoinElement types.SiacoinElement `json:"siacoinElement"`
Missed bool `json:"missed"`
Resolution types.V2FileContractResolution `json:"resolution"`
SiacoinElement types.SiacoinElement `json:"siacoinElement"`
Missed bool `json:"missed"`
}
)

Expand Down
61 changes: 30 additions & 31 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,10 @@ func AppliedEvents(cs consensus.State, b types.Block, cu ChainUpdate, relevant f
addresses[sfe.SiafundOutput.Address] = true
}

outputID := sfi.ParentID.ClaimOutputID()
if sfo, ok := sces[outputID]; ok && relevant(sfi.ClaimAddress) {
addEvent(types.Hash256(outputID), cs.MaturityHeight(), EventTypeSiafundClaim, EventPayout{
SiacoinElement: sfo,
sce, ok := sces[sfi.ParentID.ClaimOutputID()]
if ok && relevant(sce.SiacoinOutput.Address) {
addEvent(sce.ID, sce.MaturityHeight, EventTypeSiafundClaim, EventPayout{
SiacoinElement: sce,
}, []types.Address{sfi.ClaimAddress})
}
}
Expand Down Expand Up @@ -238,10 +238,10 @@ func AppliedEvents(cs consensus.State, b types.Block, cu ChainUpdate, relevant f
}
addresses[sfi.Parent.SiafundOutput.Address] = true

outputID := types.SiafundOutputID(sfi.Parent.ID).V2ClaimOutputID()
if sfo, ok := sces[outputID]; ok && relevant(sfi.ClaimAddress) {
addEvent(types.Hash256(outputID), cs.MaturityHeight(), EventTypeSiafundClaim, EventPayout{
SiacoinElement: sfo,
sce, ok := sces[types.SiafundOutputID(sfi.Parent.ID).V2ClaimOutputID()]
if ok && relevant(sfi.ClaimAddress) {
addEvent(sce.ID, sce.MaturityHeight, EventTypeSiafundClaim, EventPayout{
SiacoinElement: sce,
}, []types.Address{sfi.ClaimAddress})
}
}
Expand All @@ -265,7 +265,7 @@ func AppliedEvents(cs consensus.State, b types.Block, cu ChainUpdate, relevant f
addEvent(types.Hash256(txn.ID()), cs.Index.Height, EventTypeV2Transaction, ev, relevant) // transaction maturity height is the current block height
}

// handle missed contracts
// handle contracts
cu.ForEachFileContractElement(func(fce types.FileContractElement, _ bool, rev *types.FileContractElement, resolved, valid bool) {
if !resolved {
return
Expand All @@ -278,10 +278,10 @@ func AppliedEvents(cs consensus.State, b types.Block, cu ChainUpdate, relevant f
continue
}

outputID := types.FileContractID(fce.ID).ValidOutputID(i)
addEvent(types.Hash256(outputID), cs.MaturityHeight(), EventTypeV1ContractResolution, EventV1ContractResolution{
element := sces[types.FileContractID(fce.ID).ValidOutputID(i)]
addEvent(element.ID, element.MaturityHeight, EventTypeV1ContractResolution, EventV1ContractResolution{
Parent: fce,
SiacoinElement: sces[outputID],
SiacoinElement: element,
Missed: false,
}, []types.Address{address})
}
Expand All @@ -292,10 +292,10 @@ func AppliedEvents(cs consensus.State, b types.Block, cu ChainUpdate, relevant f
continue
}

outputID := types.FileContractID(fce.ID).MissedOutputID(i)
addEvent(types.Hash256(outputID), cs.MaturityHeight(), EventTypeV1ContractResolution, EventV1ContractResolution{
element := sces[types.FileContractID(fce.ID).MissedOutputID(i)]
addEvent(element.ID, element.MaturityHeight, EventTypeV1ContractResolution, EventV1ContractResolution{
Parent: fce,
SiacoinElement: sces[outputID],
SiacoinElement: element,
Missed: true,
}, []types.Address{address})
}
Expand All @@ -313,25 +313,25 @@ func AppliedEvents(cs consensus.State, b types.Block, cu ChainUpdate, relevant f
}

if relevant(fce.V2FileContract.HostOutput.Address) {
outputID := types.FileContractID(fce.ID).V2HostOutputID()
addEvent(types.Hash256(outputID), cs.MaturityHeight(), EventTypeV2ContractResolution, EventV2ContractResolution{
V2FileContractResolution: types.V2FileContractResolution{
element := sces[types.FileContractID(fce.ID).V2HostOutputID()]
addEvent(element.ID, element.MaturityHeight, EventTypeV2ContractResolution, EventV2ContractResolution{
Resolution: types.V2FileContractResolution{
Parent: fce,
Resolution: res,
},
SiacoinElement: sces[outputID],
SiacoinElement: element,
Missed: missed,
}, []types.Address{fce.V2FileContract.HostOutput.Address})
}

if relevant(fce.V2FileContract.RenterOutput.Address) {
outputID := types.FileContractID(fce.ID).V2RenterOutputID()
addEvent(types.Hash256(outputID), cs.MaturityHeight(), EventTypeV2ContractResolution, EventV2ContractResolution{
V2FileContractResolution: types.V2FileContractResolution{
element := sces[types.FileContractID(fce.ID).V2RenterOutputID()]
addEvent(element.ID, element.MaturityHeight, EventTypeV2ContractResolution, EventV2ContractResolution{
Resolution: types.V2FileContractResolution{
Parent: fce,
Resolution: res,
},
SiacoinElement: sces[outputID],
SiacoinElement: element,
Missed: missed,
}, []types.Address{fce.V2FileContract.RenterOutput.Address})
}
Expand All @@ -340,21 +340,20 @@ func AppliedEvents(cs consensus.State, b types.Block, cu ChainUpdate, relevant f
// handle block rewards
for i := range b.MinerPayouts {
if relevant(b.MinerPayouts[i].Address) {
outputID := cs.Index.ID.MinerOutputID(i)
addEvent(types.Hash256(outputID), cs.MaturityHeight(), EventTypeMinerPayout, EventPayout{
SiacoinElement: sces[outputID],
element := sces[cs.Index.ID.MinerOutputID(i)]
addEvent(element.ID, element.MaturityHeight, EventTypeMinerPayout, EventPayout{
SiacoinElement: element,
}, []types.Address{b.MinerPayouts[i].Address})
}
}

// handle foundation subsidy
if relevant(cs.FoundationPrimaryAddress) {
outputID := cs.Index.ID.FoundationOutputID()
sce, ok := sces[outputID]
element, ok := sces[cs.Index.ID.FoundationOutputID()]
if ok {
addEvent(types.Hash256(outputID), cs.MaturityHeight(), EventTypeFoundationSubsidy, EventPayout{
SiacoinElement: sce,
}, []types.Address{sce.SiacoinOutput.Address})
addEvent(element.ID, element.MaturityHeight, EventTypeFoundationSubsidy, EventPayout{
SiacoinElement: element,
}, []types.Address{element.SiacoinOutput.Address})
}
}

Expand Down
Loading

0 comments on commit 6f03596

Please sign in to comment.