Skip to content
Open
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
9 changes: 7 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ require (
github.com/alecthomas/participle/v2 v2.0.0-beta.5
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae
github.com/snksoft/crc v1.1.0
golang.org/x/crypto v0.17.0
golang.org/x/crypto v0.19.0
golang.org/x/exp v0.0.0-20230116083435-1de6713980de
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.34.0
)

require (
github.com/alecthomas/repr v0.1.1 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
)
19 changes: 15 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,25 @@ github.com/alecthomas/participle/v2 v2.0.0-beta.5 h1:y6dsSYVb1G5eK6mgmy+BgI3Mw35
github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM=
github.com/alecthomas/repr v0.1.1 h1:87P60cSmareLAxMc4Hro0r2RBY4ROm0dYwkJNpS4pPs=
github.com/alecthomas/repr v0.1.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae h1:7smdlrfdcZic4VfsGKD2ulWL804a4GVphr4s7WZxGiY=
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s=
github.com/snksoft/crc v1.1.0 h1:HkLdI4taFlgGGG1KvsWMpz78PkOC9TkPVpTV/cuWn48=
github.com/snksoft/crc v1.1.0/go.mod h1:5/gUOsgAm7OmIhb6WJzw7w5g2zfJi4FrHYgGPdshE+A=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20230116083435-1de6713980de h1:DBWn//IJw30uYCgERoxCg84hWtA97F4wMiKOIh00Uf0=
golang.org/x/exp v0.0.0-20230116083435-1de6713980de/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
144 changes: 144 additions & 0 deletions tychoclient/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package tychoclient

import (
"context"
"crypto/tls"
"fmt"
"time"

"google.golang.org/grpc"
"google.golang.org/grpc/credentials"

"github.com/tonkeeper/tongo/tychoclient/proto"
)

const (
// DefaultEndpoint is the testnet endpoint provided by the team
DefaultEndpoint = "tonapi-testnet.tychoprotocol.com:443"

// DefaultTimeout for gRPC calls
DefaultTimeout = 30 * time.Second
)

// Client provides access to Tycho blockchain data via gRPC
type Client struct {
conn *grpc.ClientConn
client proto.TychoIndexerClient
}

// NewClient creates a new Tycho client with default settings
func NewClient() (*Client, error) {
return NewClientWithEndpoint(DefaultEndpoint)
}

// NewClientWithEndpoint creates a new Tycho client with custom endpoint
func NewClientWithEndpoint(endpoint string) (*Client, error) {
// Use TLS for secure connection
creds := credentials.NewTLS(&tls.Config{})

conn, err := grpc.Dial(endpoint, grpc.WithTransportCredentials(creds))
if err != nil {
return nil, fmt.Errorf("failed to connect to %s: %w", endpoint, err)
}

return &Client{
conn: conn,
client: proto.NewTychoIndexerClient(conn),
}, nil
}

// Close closes the gRPC connection
func (c *Client) Close() error {
return c.conn.Close()
}

// GetStatus returns the current status of the Tycho node
func (c *Client) GetStatus(ctx context.Context) (*proto.GetStatusResponse, error) {
ctx, cancel := context.WithTimeout(ctx, DefaultTimeout)
defer cancel()

return c.client.GetStatus(ctx, &proto.GetStatusRequest{})
}

// GetLibraryCell fetches a library cell by hash
func (c *Client) GetLibraryCell(ctx context.Context, hash []byte) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, DefaultTimeout)
defer cancel()

req := &proto.GetLibraryCellRequest{
Hash: hash,
}

resp, err := c.client.GetLibraryCell(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to get library cell: %w", err)
}

switch result := resp.Library.(type) {
case *proto.GetLibraryCellResponse_NotFound:
return nil, fmt.Errorf("library cell not found")
case *proto.GetLibraryCellResponse_Found:
return result.Found.Cell, nil
default:
return nil, fmt.Errorf("unexpected response type")
}
}

// GetRawBlockData fetches raw BOC data for debugging purposes
func (c *Client) GetRawBlockData(ctx context.Context, workchain int32, shard uint64, seqno uint32) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, DefaultTimeout)
defer cancel()

req := &proto.GetBlockRequest{
Query: &proto.GetBlockRequest_BySeqno{
BySeqno: &proto.BlockBySeqno{
Workchain: workchain,
Shard: shard,
Seqno: seqno,
},
},
}

stream, err := c.client.GetBlock(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to start block stream: %w", err)
}

return c.readBlockFromStream(stream)
}

// readBlockFromStream reads block data from the gRPC stream
func (c *Client) readBlockFromStream(stream proto.TychoIndexer_GetBlockClient) ([]byte, error) {
var totalData []byte

for {
resp, err := stream.Recv()
if err != nil {
if err.Error() == "EOF" {
break
}
return nil, fmt.Errorf("stream error: %w", err)
}

switch msg := resp.Msg.(type) {
case *proto.GetBlockResponse_NotFound:
return nil, fmt.Errorf("block not found")
case *proto.GetBlockResponse_Found:
// First chunk with metadata
if msg.Found.FirstChunk != nil {
totalData = append(totalData, msg.Found.FirstChunk.Data...)
}
case *proto.GetBlockResponse_Chunk:
// Subsequent chunks
totalData = append(totalData, msg.Chunk.Data...)
default:
return nil, fmt.Errorf("unexpected response type: %T", msg)
}
}

if len(totalData) == 0 {
return nil, fmt.Errorf("no block data received")
}

return totalData, nil
}
Loading