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

Add Postgres Support #57

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/actions/test/action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
name: Test
description: Lints and tests walletd
inputs:
go-test-tags:
description: 'The tags to use when running go test'
required: false
default: 'netgo'

runs:
using: composite
Expand All @@ -19,4 +24,4 @@ runs:
- name: Test
uses: n8maninger/action-golang-test@v1
with:
args: "-race;-tags=testing netgo"
args: "-race;-tags=${{ inputs.go-test-tags }}"
42 changes: 39 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,48 @@ env:
CGO_ENABLED: 1

jobs:
test:
test-linux:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
go-version: [ '1.21', '1.22' ]
services:
postgres:
image: postgres
env:
POSTGRES_USER: walletd_test
POSTGRES_PASSWORD: walletd_test
POSTGRES_DB: walletd_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Configure git
run: git config --global core.autocrlf false # required on Windows
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
- name: Test
uses: ./.github/actions/test
with:
go-test-tags: 'test_pg'
- name: Build
run: go build -o bin/ ./cmd/walletd
test-other:
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
matrix:
os: [ ubuntu-latest , macos-latest, windows-latest ]
go-version: [ '1.20', '1.21' ]
os: [ macos-latest, windows-latest ]
go-version: [ '1.21', '1.22' ]
steps:
- name: Configure git
run: git config --global core.autocrlf false # required on Windows
Expand All @@ -27,5 +61,7 @@ jobs:
go-version: ${{ matrix.go-version }}
- name: Test
uses: ./.github/actions/test
with:
go-test-tags: 'netgo'
- name: Build
run: go build -o bin/ ./cmd/walletd
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module go.sia.tech/walletd
go 1.21

require (
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.21
go.sia.tech/core v0.2.1
go.sia.tech/coreutils v0.0.0-20240130201319-8303550528d7
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.21 h1:IXocQLOykluc3xPE0Lvy8FtggMz1G+U3mEjg+0zGizc=
github.com/mattn/go-sqlite3 v1.14.21/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
85 changes: 85 additions & 0 deletions persist/peers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package persist_test

import (
"net"
"testing"
"time"

"go.sia.tech/coreutils/syncer"
"go.uber.org/zap"
"go.uber.org/zap/zaptest"
)

func testPeers(storeFn func(t *testing.T, log *zap.Logger) syncer.PeerStore) func(t *testing.T) {
return func(t *testing.T) {
log := zaptest.NewLogger(t)
db := storeFn(t, log)

const peer = "1.2.3.4:9981"

db.AddPeer(peer)

lastConnect := time.Now().Truncate(time.Second) // stored as unix milliseconds
syncedBlocks := uint64(15)
syncDuration := 5 * time.Second

db.UpdatePeerInfo(peer, func(info *syncer.PeerInfo) {
info.LastConnect = lastConnect
info.SyncedBlocks = syncedBlocks
info.SyncDuration = syncDuration
})

info, ok := db.PeerInfo(peer)
if !ok {
t.Fatal("expected peer to be in database")
}

if !info.LastConnect.Equal(lastConnect) {
t.Errorf("expected LastConnect = %v; got %v", lastConnect, info.LastConnect)
}
if info.SyncedBlocks != syncedBlocks {
t.Errorf("expected SyncedBlocks = %d; got %d", syncedBlocks, info.SyncedBlocks)
}
if info.SyncDuration != 5*time.Second {
t.Errorf("expected SyncDuration = %s; got %s", syncDuration, info.SyncDuration)
}
}
}

func testBanPeer(storeFn func(t *testing.T, log *zap.Logger) syncer.PeerStore) func(t *testing.T) {
return func(t *testing.T) {
log := zaptest.NewLogger(t)
db := storeFn(t, log)
const peer = "1.2.3.4"

if db.Banned(peer) {
t.Fatal("expected peer to not be banned")
}

// ban the peer
db.Ban(peer, time.Second, "test")

if !db.Banned(peer) {
t.Fatal("expected peer to be banned")
}

// wait for the ban to expire
time.Sleep(time.Second)

if db.Banned(peer) {
t.Fatal("expected peer to not be banned")
}

// ban a subnet
_, subnet, err := net.ParseCIDR(peer + "/24")
if err != nil {
t.Fatal(err)
}

t.Log("banning", subnet)
db.Ban(subnet.String(), time.Second, "test")
if !db.Banned(peer) {
t.Fatal("expected peer to be banned")
}
}
}
48 changes: 48 additions & 0 deletions persist/postgres/addresses.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package postgres

import (
"fmt"

"go.sia.tech/core/types"
"go.sia.tech/walletd/wallet"
)

// AddressBalance returns the balance of a single address.
func (s *Store) AddressBalance(address types.Address) (balance wallet.Balance, err error) {
err = s.transaction(func(tx *txn) error {
const query = `SELECT siacoin_balance, immature_siacoin_balance, siafund_balance FROM sia_addresses WHERE sia_address=$1`
return tx.QueryRow(query, encode(address)).Scan(decode(&balance.Siacoins), decode(&balance.ImmatureSiacoins), &balance.Siafunds)
})
return
}

// AddressEvents returns the events of a single address.
func (s *Store) AddressEvents(address types.Address, limit, offset int) (events []wallet.Event, err error) {
err = s.transaction(func(tx *txn) error {
const query = `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
INNER JOIN chain_indices ci ON (ev.index_id = ci.id)
INNER JOIN event_addresses ea ON (ev.id = ea.event_id)
INNER JOIN sia_addresses sa ON (ea.address_id = sa.id)
WHERE sa.sia_address = $1
ORDER BY ev.maturity_height DESC, ev.id DESC
LIMIT $2 OFFSET $3`

rows, err := tx.Query(query, encode(address), limit, offset)
if err != nil {
return err
}
defer rows.Close()

for rows.Next() {
event, _, err := scanEvent(rows)
if err != nil {
return fmt.Errorf("failed to scan event: %w", err)
}

events = append(events, event)
}
return rows.Err()
})
return
}
Loading
Loading