Skip to content
Merged
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
26 changes: 15 additions & 11 deletions blockchain/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func retry2[T1 any, T2 any](fn func() (T1, T2, error)) (T1, T2, error) {
}

// NewConnection creates new Blockchain connection
func NewConnection(addr, key string, rateLimit int) (*Connection, error) {
func NewConnection(addr, key string, rateLimit int, blockStorage provenBlocksStorage) (*Connection, error) {

client := liteclient.NewConnectionPool()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*120)
Expand All @@ -103,26 +103,30 @@ func NewConnection(addr, key string, rateLimit int) (*Connection, error) {
var wrappedClient ton.APIClientWrapped

if config.Config.ProofCheckEnabled {
wrappedClient = ton.NewAPIClient(limitedClient, ton.ProofCheckPolicySecure)

if config.Config.NetworkConfigUrl == "" {
return nil, fmt.Errorf("empty network config URL")
}
lastBlock, err := blockStorage.GetLastMasterchainProvenBlock(ctx)
if err == nil {
wrappedClient.SetTrustedBlock(lastBlock)
} else {
if config.Config.NetworkConfigUrl == "" {
return nil, fmt.Errorf("empty network config URL")
}

cfg, err := liteclient.GetConfigFromUrl(ctx, config.Config.NetworkConfigUrl)
if err != nil {
return nil, fmt.Errorf("get network config from url err: %s", err.Error())
cfg, err := liteclient.GetConfigFromUrl(ctx, config.Config.NetworkConfigUrl)
if err != nil {
return nil, fmt.Errorf("get network config from url err: %s", err.Error())
}
wrappedClient.SetTrustedBlockFromConfig(cfg)
}

wrappedClient = ton.NewAPIClient(limitedClient, ton.ProofCheckPolicySecure)
wrappedClient.SetTrustedBlockFromConfig(cfg)

log.Infof("Fetching and checking proofs since config init block ...")
_, err = wrappedClient.CurrentMasterchainInfo(ctx) // we fetch block just to trigger chain proof check
if err != nil {
return nil, fmt.Errorf("get masterchain info err: %s", err.Error())
}
log.Infof("Proof checks are completed")

go updateLastBlocks(wrappedClient, blockStorage)
} else {
wrappedClient = ton.NewAPIClient(limitedClient, ton.ProofCheckPolicyUnsafe)
}
Expand Down
30 changes: 24 additions & 6 deletions blockchain/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ package blockchain
import (
"bytes"
"context"
"github.com/gobicycle/bicycle/core"
"github.com/xssnick/tonutils-go/address"
"github.com/xssnick/tonutils-go/tlb"
"github.com/xssnick/tonutils-go/ton/jetton"
"github.com/xssnick/tonutils-go/ton/wallet"
"math/big"
"math/rand"
"os"
"testing"
"time"

"github.com/gobicycle/bicycle/core"
"github.com/xssnick/tonutils-go/address"
"github.com/xssnick/tonutils-go/tlb"
"github.com/xssnick/tonutils-go/ton"
"github.com/xssnick/tonutils-go/ton/jetton"
"github.com/xssnick/tonutils-go/ton/wallet"
)

var (
Expand All @@ -21,6 +23,22 @@ var (
notActiveAccount, _ = address.ParseAddr("kQAkRRJ1RiViVHY2UmUhWCFjdiZBeEYnhkhxI1JTJFNUNG9v")
)

type mockBlockStorage struct{}

func (m mockBlockStorage) SaveLastMasterchainProvenBlock(ctx context.Context, block ton.BlockIDExt) error {
return nil
}

func (m mockBlockStorage) GetLastMasterchainProvenBlock(ctx context.Context) (*ton.BlockIDExt, error) {
return &ton.BlockIDExt{
Workchain: -1,
Shard: -8000000000000000,
SeqNo: 69722697,
RootHash: []byte{0x4b, 0x7e, 0x27, 0xd3, 0xcc, 0x60, 0xbf, 0x16, 0xc7, 0x1b, 0xe0, 0x2d, 0xe8, 0xee, 0xb1, 0xb8, 0x74, 0xc9, 0x1d, 0xe6, 0x49, 0xe6, 0x2f, 0x3a, 0xd0, 0x88, 0xd5, 0xeb, 0x7a, 0x6a, 0x8d, 0x30},
FileHash: []byte{0x41, 0x89, 0x69, 0xd0, 0x94, 0x1d, 0x94, 0x63, 0xa1, 0x58, 0xbf, 0xfe, 0x27, 0x38, 0x52, 0xe3, 0x4b, 0x35, 0x13, 0x4a, 0x38, 0xde, 0xf0, 0x26, 0x90, 0xec, 0xc8, 0x95, 0xb5, 0x10, 0xba, 0xa8},
}, nil
}

func connect(t *testing.T) *Connection {
server := os.Getenv("SERVER")
if server == "" {
Expand All @@ -30,7 +48,7 @@ func connect(t *testing.T) *Connection {
if key == "" {
t.Fatal("empty key var")
}
c, err := NewConnection(server, key, 100)
c, err := NewConnection(server, key, 100, mockBlockStorage{})
if err != nil {
t.Fatal("connections err: ", err)
}
Expand Down
29 changes: 29 additions & 0 deletions blockchain/proofs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package blockchain

import (
"context"
"time"

log "github.com/sirupsen/logrus"
"github.com/xssnick/tonutils-go/ton"
)

type provenBlocksStorage interface {
SaveLastMasterchainProvenBlock(ctx context.Context, block ton.BlockIDExt) error
GetLastMasterchainProvenBlock(ctx context.Context) (*ton.BlockIDExt, error)
}

func updateLastBlocks(client ton.APIClientWrapped, storage provenBlocksStorage) {
for {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
last, err := client.GetMasterchainInfo(ctx)
if err == nil {
err := storage.SaveLastMasterchainProvenBlock(ctx, *last)
if err != nil {
log.Errorf("save last masterchain block err: %v. If you are using PROOF_CHECK_ENABLED please make migration 0.10.x-0.11.0.sql ", err)
}
}
cancel()
time.Sleep(time.Minute * 10)
}
}
21 changes: 11 additions & 10 deletions cmd/processor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import (
"context"
"errors"
"fmt"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"

"github.com/gobicycle/bicycle/api"
"github.com/gobicycle/bicycle/blockchain"
"github.com/gobicycle/bicycle/config"
Expand All @@ -12,12 +19,6 @@ import (
"github.com/gobicycle/bicycle/queue"
"github.com/gobicycle/bicycle/webhook"
log "github.com/sirupsen/logrus"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"
)

var Version = "dev"
Expand All @@ -32,14 +33,14 @@ func main() {
signal.Notify(sigChannel, os.Interrupt, syscall.SIGTERM)
wg := new(sync.WaitGroup)

bcClient, err := blockchain.NewConnection(config.Config.LiteServer, config.Config.LiteServerKey, config.Config.LiteServerRateLimit)
dbClient, err := db.NewConnection(config.Config.DatabaseURI)
if err != nil {
log.Fatalf("blockchain connection error: %v", err)
log.Fatalf("DB connection error: %v", err)
}

dbClient, err := db.NewConnection(config.Config.DatabaseURI)
bcClient, err := blockchain.NewConnection(config.Config.LiteServer, config.Config.LiteServerKey, config.Config.LiteServerRateLimit, dbClient)
if err != nil {
log.Fatalf("DB connection error: %v", err)
log.Fatalf("blockchain connection error: %v", err)
}

ctx, cancel := context.WithTimeout(context.Background(), time.Second*120)
Expand Down
2 changes: 1 addition & 1 deletion cmd/testutil/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func main() {
log.Fatalf("invalid HOT_WALLET_B env var")
}

bcClient, err := blockchain.NewConnection(config.Config.LiteServer, config.Config.LiteServerKey, config.Config.LiteServerRateLimit)
bcClient, err := blockchain.NewConnection(config.Config.LiteServer, config.Config.LiteServerKey, config.Config.LiteServerRateLimit, nil)
if err != nil {
log.Fatalf("blockchain connection error: %v", err)
}
Expand Down
35 changes: 35 additions & 0 deletions db/proofs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package db

import (
"context"

"github.com/xssnick/tonutils-go/ton"
)

func (c *Connection) SaveLastMasterchainProvenBlock(ctx context.Context, block ton.BlockIDExt) error {
_, err := c.client.Exec(ctx, `
INSERT INTO payments.last_proven_block (
slug,
workchain,
shard,
seqno,
root_hash,
file_hash
) VALUES ('head', $1, $2, $3, $4, $5)
ON CONFLICT (slug) DO UPDATE SET workchain = $1, shard = $2, seqno = $3, root_hash = $4, file_hash = $5
`, block.Workchain,
block.Shard,
block.SeqNo,
block.RootHash,
block.FileHash,
)
return err
}

func (c *Connection) GetLastMasterchainProvenBlock(ctx context.Context) (*ton.BlockIDExt, error) {
var block ton.BlockIDExt
err := c.client.
QueryRow(ctx, `SELECT workchain, shard, seqno, root_hash, file_hash FROM payments.last_proven_block WHERE slug = 'head'`).
Scan(&block.Workchain, &block.Shard, &block.SeqNo, &block.RootHash, &block.FileHash)
return &block, err
}
1 change: 1 addition & 0 deletions deploy/db/01_init.down.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ DROP TABLE IF EXISTS payments.external_incomes;
DROP TABLE IF EXISTS payments.block_data;
DROP TABLE IF EXISTS payments.internal_withdrawals;
DROP TABLE IF EXISTS payments.service_withdrawal_requests;
DROP TABLE IF EXISTS payments.last_proven_block;

DROP SCHEMA IF EXISTS payments;

Expand Down
9 changes: 9 additions & 0 deletions deploy/db/01_init.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,13 @@ CREATE TABLE IF NOT EXISTS payments.service_withdrawal_requests
jetton_master bytea
);

CREATE TABLE IF NOT EXISTS payments.last_proven_block (
slug text PRIMARY KEY,
workchain bigint not null,
shard bigint not null,
seqno bigint not null,
root_hash bytea not null,
file_hash bytea not null
);

COMMIT;
8 changes: 8 additions & 0 deletions deploy/manual_migrations/0.10.x-0.11.0.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS payments.last_proven_block (
slug text PRIMARY KEY,
workchain bigint not null,
shard bigint not null,
seqno bigint not null,
root_hash bytea not null,
file_hash bytea not null
);
15 changes: 15 additions & 0 deletions manual_migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,18 @@ will be filled with a 0 workchain.
2. Build new docker image and recreate container for `payment-processor` as described in `Service deploy` chapter in [Readme](/README.md)

Note that this query creates a new nullable column in the `external_withdrawals` and `external_incomes` DB tables and `binary_comment` column in `withdrawal_requests` table.

## 0.10.x -> 0.11.0
(**Optional**. Recommended if you are using proofs checking (PROOF_CHECK_ENABLED=true))
1. Apply [DB migration](/deploy/manual_migrations/0.10.x-0.11.0.sql)
2. Build new docker image and recreate container for `payment-processor` as described in `Service deploy` chapter in [Readme](/README.md)

Note that this query creates a new table `payments.last_proven_block`.
It will be filled automatically based on config file (NETWORK_CONFIG_URL) and archive node, but you can skip requirements for an archive node and fill it manually:

Go to https://tonviewer.com/last, take **seqno**, **root_hash** and **file_hash** and put them into the request below.
For example 69722697, 4b7e27d3cc60bf16c71be02de8eeb1b874c91de649e62f3ad088d5eb7a6a8d30, 418969d0941d9463a158bffe273852e34b35134a38def02690ecc895b510baa8
```sql
INSERT INTO payments.last_proven_block ( slug, workchain, shard, seqno, root_hash, file_hash)
VALUES ('head', -1, -9223372036854775808, 69722697, E'\\x4b7e27d3cc60bf16c71be02de8eeb1b874c91de649e62f3ad088d5eb7a6a8d30', E'\\x418969d0941d9463a158bffe273852e34b35134a38def02690ecc895b510baa8')
```
Loading