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
33 changes: 10 additions & 23 deletions book/src/framework/components/blockchains/canton.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,9 @@ import (
"fmt"
"strings"
"testing"
"time"

"github.com/fullstorydev/grpcurl"
"github.com/go-resty/resty/v2"
"github.com/golang-jwt/jwt/v5"
"github.com/jhump/protoreflect/grpcreflect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand All @@ -79,7 +77,6 @@ import (

"github.com/smartcontractkit/chainlink-testing-framework/framework"
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain/canton"
)

type CfgCanton struct {
Expand All @@ -94,52 +91,42 @@ func TestCantonSmoke(t *testing.T) {
require.NoError(t, err)

t.Run("Test scan endpoint", func(t *testing.T) {
resp, err := resty.New().SetBaseURL(bc.NetworkSpecificData.CantonEndpoints.ScanAPIURL).R().
resp, err := resty.New().SetBaseURL(bc.NetworkSpecificData.CantonData.ExternalEndpoints.ScanAPIURL).R().
Get("/v0/dso-party-id")
assert.NoError(t, err)
fmt.Println(resp)
})
t.Run("Test registry endpoint", func(t *testing.T) {
resp, err := resty.New().SetBaseURL(bc.NetworkSpecificData.CantonEndpoints.RegistryAPIURL).R().
Get("/metadata/v1/instruments")
resp, err := resty.New().SetBaseURL(bc.NetworkSpecificData.CantonData.ExternalEndpoints.RegistryAPIURL).R().
Get("/registry/metadata/v1/instruments")
assert.NoError(t, err)
fmt.Println(resp)
})

testParticipant := func(t *testing.T, name string, endpoints blockchain.CantonParticipantEndpoints) {
t.Run(fmt.Sprintf("Test %s endpoints", name), func(t *testing.T) {
j, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
Issuer: "",
Subject: fmt.Sprintf("user-%s", name),
Audience: []string{canton.AuthProviderAudience},
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)),
NotBefore: jwt.NewNumericDate(time.Now()),
IssuedAt: jwt.NewNumericDate(time.Now()),
ID: "",
}).SignedString([]byte(canton.AuthProviderSecret))

// JSON Ledger API
fmt.Println("Calling JSON Ledger API")
resp, err := resty.New().SetBaseURL(endpoints.JSONLedgerAPIURL).SetAuthToken(j).R().
resp, err := resty.New().SetBaseURL(endpoints.JSONLedgerAPIURL).SetAuthToken(endpoints.JWT).R().
Get("/v2/packages")
assert.NoError(t, err)
fmt.Println(resp)

// gRPC Ledger API - use reflection
fmt.Println("Calling gRPC Ledger API")
res, err := callGRPC(t.Context(), endpoints.GRPCLedgerAPIURL, "com.daml.ledger.api.v2.admin.PartyManagementService/GetParties", `{}`, []string{fmt.Sprintf("Authorization: Bearer %s", j)})
res, err := callGRPC(t.Context(), endpoints.GRPCLedgerAPIURL, "com.daml.ledger.api.v2.admin.PartyManagementService/GetParties", `{}`, []string{fmt.Sprintf("Authorization: Bearer %s", endpoints.JWT)})
assert.NoError(t, err)
fmt.Println(res)

// gRPC Admin API - use reflection
fmt.Println("Calling gRPC Admin API")
res, err = callGRPC(t.Context(), endpoints.AdminAPIURL, "com.digitalasset.canton.admin.participant.v30.PackageService/ListDars", `{}`, []string{fmt.Sprintf("Authorization: Bearer %s", j)})
res, err = callGRPC(t.Context(), endpoints.AdminAPIURL, "com.digitalasset.canton.admin.participant.v30.PackageService/ListDars", `{}`, []string{fmt.Sprintf("Authorization: Bearer %s", endpoints.JWT)})
assert.NoError(t, err)
fmt.Println(res)

// Validator API
fmt.Println("Calling Validator API")
resp, err = resty.New().SetBaseURL(endpoints.ValidatorAPIURL).SetAuthToken(j).R().
resp, err = resty.New().SetBaseURL(endpoints.ValidatorAPIURL).SetAuthToken(endpoints.JWT).R().
Get("/v0/admin/users")
assert.NoError(t, err)
fmt.Println(resp)
Expand All @@ -160,9 +147,9 @@ func TestCantonSmoke(t *testing.T) {
}

// Call all participants, starting with the SV
testParticipant(t, "sv", bc.NetworkSpecificData.CantonEndpoints.SuperValidator)
testParticipant(t, "sv", bc.NetworkSpecificData.CantonData.ExternalEndpoints.SuperValidator)
for i := 1; i <= in.BlockchainA.NumberOfCantonValidators; i++ {
testParticipant(t, fmt.Sprintf("participant%d", i), bc.NetworkSpecificData.CantonEndpoints.Participants[i-1])
testParticipant(t, fmt.Sprintf("participant%d", i), bc.NetworkSpecificData.CantonData.ExternalEndpoints.Participants[i-1])
}
}

Expand Down Expand Up @@ -199,4 +186,4 @@ func callGRPC(ctx context.Context, url string, method string, jsonRequest string
return output.String(), nil
}

```
```
3 changes: 3 additions & 0 deletions framework/.changeset/v0.15.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Update the Canton blockchain containers to support Docker-internal networking
- Change the `NetworkSpecificData` for Canton to be of type `CantonData` and split internal/external endpoints into separate fields
- Bump the Canton and Splice images to `0.5.13`
6 changes: 3 additions & 3 deletions framework/components/blockchain/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ type Output struct {
}

type NetworkSpecificData struct {
SuiAccount *SuiWalletInfo `toml:"sui_account" comment:"Sui network account info"`
CantonEndpoints *CantonEndpoints `toml:"canton_endpoints" comment:"Canton network endpoints info"`
StellarNetwork *StellarNetworkInfo `toml:"stellar_network" comment:"Stellar network info"`
SuiAccount *SuiWalletInfo `toml:"sui_account" comment:"Sui network account info"`
CantonData *CantonData `toml:"canton_data" comment:"Canton network data"`
StellarNetwork *StellarNetworkInfo `toml:"stellar_network" comment:"Stellar network info"`
}

// Node represents blockchain node output, URLs required for connection locally and inside docker network
Expand Down
74 changes: 55 additions & 19 deletions framework/components/blockchain/canton.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,21 @@ const (
TokenExpiry = time.Hour * 24 * 365 * 10 // 10 years
)

type CantonData struct {
// Docker internal endpoints, only reachable if connected to the same Docker network (framework.DefaultNetworkName)
InternalEndpoints CantonEndpoints `toml:"internal_endpoints" comment:"Docker-internal endpoints, only reachable from containers connected to the same networks"`
// External endpoints, reachable from the Docker host
ExternalEndpoints CantonEndpoints `toml:"external_endpoints" comment:"Docker-external endpoints, only reachable from the Docker host"`
}

type CantonEndpoints struct {
// ScanAPIURL https://docs.sync.global/app_dev/scan_api/index.html
ScanAPIURL string `toml:"scan_api_url" comment:"https://docs.sync.global/app_dev/scan_api/index.html"`
// RegistryAPIURL https://docs.sync.global/app_dev/token_standard/index.html#api-references
RegistryAPIURL string `toml:"registry_api_url" comment:"https://docs.sync.global/app_dev/token_standard/index.html#api-references"`

// SuperValidator The endpoints for the super validator
SuperValidator CantonParticipantEndpoints `toml:"super_validator" comment:"Canton network super validator"`
SuperValidator CantonParticipantEndpoints `toml:"super_validator" comment:"Canton super validator endpoints"`
// Participants The endpoints for the participants, in order from participant1 to participantN - depending on the number of validators requested
Participants []CantonParticipantEndpoints `toml:"participants" comment:"Canton participant endpoints"`
}
Expand Down Expand Up @@ -119,7 +126,7 @@ func newCanton(ctx context.Context, in *Input) (*Output, error) {
}

// Set up Nginx container
nginxReq := canton.NginxContainerRequest(in.NumberOfCantonValidators, in.Port, cantonReq.Name, spliceReq.Name)
nginxReq, nginxContainerName := canton.NginxContainerRequest(in.NumberOfCantonValidators, in.Port, cantonReq.Name, spliceReq.Name)
nginxContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: nginxReq,
Started: true,
Expand All @@ -133,6 +140,7 @@ func newCanton(ctx context.Context, in *Input) (*Output, error) {
return nil, err
}

// Add SV info to output
svUser := "user-sv"
svToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
Issuer: "",
Expand All @@ -146,21 +154,40 @@ func newCanton(ctx context.Context, in *Input) (*Output, error) {
if err != nil {
return nil, fmt.Errorf("failed to create token for sv: %w", err)
}
endpoints := &CantonEndpoints{
ScanAPIURL: fmt.Sprintf("http://scan.%s:%s/api/scan", host, in.Port),
RegistryAPIURL: fmt.Sprintf("http://scan.%s:%s", host, in.Port), // Don't add /registry to URL as this is part of the OpenAPI spec and the base URL should point to the root
SuperValidator: CantonParticipantEndpoints{
JSONLedgerAPIURL: fmt.Sprintf("http://sv.json-ledger-api.%s:%s", host, in.Port),
GRPCLedgerAPIURL: fmt.Sprintf("sv.grpc-ledger-api.%s:%s", host, in.Port),
AdminAPIURL: fmt.Sprintf("sv.admin-api.%s:%s", host, in.Port),
ValidatorAPIURL: fmt.Sprintf("http://sv.validator-api.%s:%s/api/validator", host, in.Port),
HTTPHealthCheckURL: fmt.Sprintf("http://sv.http-health-check.%s:%s", host, in.Port),
GRPCHealthCheckURL: fmt.Sprintf("sv.grpc-health-check.%s:%s", host, in.Port),
UserID: svUser,
JWT: svToken,
data := &CantonData{
InternalEndpoints: CantonEndpoints{
ScanAPIURL: fmt.Sprintf("http://scan.%s:%d/api/scan", nginxContainerName, canton.DefaultNginxInternalPort),
RegistryAPIURL: fmt.Sprintf("http://scan.%s:%d", nginxContainerName, canton.DefaultNginxInternalPort), // Don't add /registry to URL as this is part of the OpenAPI spec and the base URL should point to the root
SuperValidator: CantonParticipantEndpoints{
JSONLedgerAPIURL: fmt.Sprintf("http://sv.json-ledger-api.%s:%d", nginxContainerName, canton.DefaultNginxInternalPort),
GRPCLedgerAPIURL: fmt.Sprintf("sv.grpc-ledger-api.%s:%d", nginxContainerName, canton.DefaultNginxInternalPort),
AdminAPIURL: fmt.Sprintf("sv.admin-api.%s:%d", nginxContainerName, canton.DefaultNginxInternalPort),
ValidatorAPIURL: fmt.Sprintf("http://sv.validator-api.%s:%d/api/validator", nginxContainerName, canton.DefaultNginxInternalPort),
HTTPHealthCheckURL: fmt.Sprintf("http://sv.http-health-check.%s:%d", nginxContainerName, canton.DefaultNginxInternalPort),
GRPCHealthCheckURL: fmt.Sprintf("sv.grpc-health-check.%s:%d", nginxContainerName, canton.DefaultNginxInternalPort),
UserID: svUser,
JWT: svToken,
},
Participants: make([]CantonParticipantEndpoints, 0, in.NumberOfCantonValidators),
},
ExternalEndpoints: CantonEndpoints{
ScanAPIURL: fmt.Sprintf("http://scan.%s:%s/api/scan", host, in.Port),
RegistryAPIURL: fmt.Sprintf("http://scan.%s:%s", host, in.Port), // Don't add /registry to URL as this is part of the OpenAPI spec and the base URL should point to the root
SuperValidator: CantonParticipantEndpoints{
JSONLedgerAPIURL: fmt.Sprintf("http://sv.json-ledger-api.%s:%s", host, in.Port),
GRPCLedgerAPIURL: fmt.Sprintf("sv.grpc-ledger-api.%s:%s", host, in.Port),
AdminAPIURL: fmt.Sprintf("sv.admin-api.%s:%s", host, in.Port),
ValidatorAPIURL: fmt.Sprintf("http://sv.validator-api.%s:%s/api/validator", host, in.Port),
HTTPHealthCheckURL: fmt.Sprintf("http://sv.http-health-check.%s:%s", host, in.Port),
GRPCHealthCheckURL: fmt.Sprintf("sv.grpc-health-check.%s:%s", host, in.Port),
UserID: svUser,
JWT: svToken,
},
Participants: make([]CantonParticipantEndpoints, 0, in.NumberOfCantonValidators),
},
Participants: nil,
}

// Add Participant info to output
for i := 1; i <= in.NumberOfCantonValidators; i++ {
participantUser := fmt.Sprintf("user-participant%v", i)
token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
Expand All @@ -175,7 +202,17 @@ func newCanton(ctx context.Context, in *Input) (*Output, error) {
if err != nil {
return nil, fmt.Errorf("failed to create token for participant%v: %w", i, err)
}
participantEndpoints := CantonParticipantEndpoints{
data.InternalEndpoints.Participants = append(data.InternalEndpoints.Participants, CantonParticipantEndpoints{
JSONLedgerAPIURL: fmt.Sprintf("http://participant%d.json-ledger-api.%s:%d", i, nginxContainerName, canton.DefaultNginxInternalPort),
GRPCLedgerAPIURL: fmt.Sprintf("participant%d.grpc-ledger-api.%s:%d", i, nginxContainerName, canton.DefaultNginxInternalPort),
AdminAPIURL: fmt.Sprintf("participant%d.admin-api.%s:%d", i, nginxContainerName, canton.DefaultNginxInternalPort),
ValidatorAPIURL: fmt.Sprintf("http://participant%d.validator-api.%s:%d/api/validator", i, nginxContainerName, canton.DefaultNginxInternalPort),
HTTPHealthCheckURL: fmt.Sprintf("http://participant%d.http-health-check.%s:%d", i, nginxContainerName, canton.DefaultNginxInternalPort),
GRPCHealthCheckURL: fmt.Sprintf("participant%d.grpc-health-check.%s:%d", i, nginxContainerName, canton.DefaultNginxInternalPort),
UserID: participantUser,
JWT: token,
})
data.ExternalEndpoints.Participants = append(data.ExternalEndpoints.Participants, CantonParticipantEndpoints{
JSONLedgerAPIURL: fmt.Sprintf("http://participant%d.json-ledger-api.%s:%s", i, host, in.Port),
GRPCLedgerAPIURL: fmt.Sprintf("participant%d.grpc-ledger-api.%s:%s", i, host, in.Port),
AdminAPIURL: fmt.Sprintf("participant%d.admin-api.%s:%s", i, host, in.Port),
Expand All @@ -184,8 +221,7 @@ func newCanton(ctx context.Context, in *Input) (*Output, error) {
GRPCHealthCheckURL: fmt.Sprintf("participant%d.grpc-health-check.%s:%s", i, host, in.Port),
UserID: participantUser,
JWT: token,
}
endpoints.Participants = append(endpoints.Participants, participantEndpoints)
})
}

return &Output{
Expand All @@ -195,7 +231,7 @@ func newCanton(ctx context.Context, in *Input) (*Output, error) {
ChainID: in.ChainID,
ContainerName: nginxReq.Name,
NetworkSpecificData: &NetworkSpecificData{
CantonEndpoints: endpoints,
CantonData: data,
},
}, nil
}
2 changes: 1 addition & 1 deletion framework/components/blockchain/canton/canton.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

// Canton Defaults
const (
SpliceVersion = "0.5.11"
SpliceVersion = "0.5.13"
Image = "ghcr.io/digital-asset/decentralized-canton-sync/docker/canton"
)

Expand Down
Loading
Loading