Skip to content

Commit

Permalink
feat: Multiple GSRPC version support (#31)
Browse files Browse the repository at this point in the history
Co-authored-by: Freddy Li <[email protected]>
  • Loading branch information
freddyli7 and Freddy Li authored Aug 19, 2024
1 parent 258996b commit e626c7d
Show file tree
Hide file tree
Showing 6 changed files with 382 additions and 10 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.19
go-version: 1.21

- uses: actions/checkout@v2

Expand All @@ -25,7 +25,7 @@ jobs:
steps:
- uses: actions/setup-go@v2
with:
go-version: "1.19.x"
go-version: "1.21.x"
- uses: actions/checkout@v2

- name: Run go vet
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.19.x]
go-version: [1.21.x]
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
Expand Down
214 changes: 214 additions & 0 deletions chains/substrate/client/check_metadata_hash_enabled_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
// The Licensed Work is (c) 2022 Sygma
// SPDX-License-Identifier: LGPL-3.0-only

package client

import (
"bytes"
"context"
"fmt"
"math/big"
"sync"
"time"

"github.com/rs/zerolog/log"
"github.com/sygmaprotocol/go-substrate-rpc-client/v4/rpc/author"
"github.com/sygmaprotocol/go-substrate-rpc-client/v4/scale"
"github.com/sygmaprotocol/go-substrate-rpc-client/v4/signature"
"github.com/sygmaprotocol/go-substrate-rpc-client/v4/types"
"github.com/sygmaprotocol/sygma-core/chains/substrate/connection"
"github.com/sygmaprotocol/sygma-core/chains/substrate/events"
)

type SubstrateCheckMetadataModeEnabledClient struct {
key *signature.KeyringPair // Keyring used for signing
nonceLock sync.Mutex // Locks nonce for updates
nonce types.U32 // Latest account nonce
tip uint64
Conn *connection.CheckMetadataModeEnabledConnection
ChainID *big.Int
}

func NewSubstrateCheckMetadataModeEnabledClient(conn *connection.CheckMetadataModeEnabledConnection, key *signature.KeyringPair, chainID *big.Int, tip uint64) *SubstrateCheckMetadataModeEnabledClient {
return &SubstrateCheckMetadataModeEnabledClient{
key: key,
Conn: conn,
ChainID: chainID,
tip: tip,
}
}

// Transact constructs and submits an extrinsic to call the method with the given arguments.
// All args are passed directly into GSRPC. GSRPC types are recommended to avoid serialization inconsistencies.
func (c *SubstrateCheckMetadataModeEnabledClient) Transact(method string, args ...interface{}) (types.Hash, *author.ExtrinsicStatusSubscription, error) {
log.Debug().Msgf("Submitting substrate call... method %s, sender %s", method, c.key.Address)

// Create call and extrinsic
meta := c.Conn.GetMetadata()
call, err := types.NewCall(
&meta,
method,
args...,
)
if err != nil {
return types.Hash{}, nil, fmt.Errorf("failed to construct call: %w", err)
}

ext := types.NewExtrinsic(call)
// Get latest runtime version
rv, err := c.Conn.RPC.State.GetRuntimeVersionLatest()
if err != nil {
return types.Hash{}, nil, err
}

c.nonceLock.Lock()
defer c.nonceLock.Unlock()

nonce, err := c.nextNonce(&meta)
if err != nil {
return types.Hash{}, nil, err
}

// Sign the extrinsic
o := types.SignatureOptions{
BlockHash: types.Hash(c.Conn.GenesisHash),
Era: types.ExtrinsicEra{IsMortalEra: false},
GenesisHash: types.Hash(c.Conn.GenesisHash),
Nonce: types.NewUCompactFromUInt(uint64(nonce)),
SpecVersion: types.U32(rv.SpecVersion),
Tip: types.NewUCompactFromUInt(c.tip),
TransactionVersion: types.U32(rv.TransactionVersion),
}

sub, err := c.submitAndWatchExtrinsic(o, &ext)
if err != nil {
return types.Hash{}, nil, fmt.Errorf("submission of extrinsic failed: %w", err)
}

hash, err := ExtrinsicHashCheckMetadataModeEnabled(ext)
if err != nil {
return types.Hash{}, nil, err
}

log.Info().Str("extrinsic", hash.Hex()).Msgf("Extrinsic call submitted... method %s, sender %s, nonce %d", method, c.key.Address, nonce)
c.nonce = nonce + 1

return hash, sub, nil
}

func (c *SubstrateCheckMetadataModeEnabledClient) TrackExtrinsic(extHash types.Hash, sub *author.ExtrinsicStatusSubscription) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Minute*10))
defer sub.Unsubscribe()
defer cancel()
subChan := sub.Chan()
for {
select {
case status := <-subChan:
{
if status.IsInBlock {
log.Debug().Str("extrinsic", extHash.Hex()).Msgf("Extrinsic in block with hash: %#x", status.AsInBlock)
}
if status.IsFinalized {
log.Info().Str("extrinsic", extHash.Hex()).Msgf("Extrinsic is finalized in block with hash: %#x", status.AsFinalized)
return c.checkExtrinsicSuccess(extHash, types.Hash(status.AsFinalized))
}
}
case <-ctx.Done():
return fmt.Errorf("extrinsic has timed out")
}
}
}

func (c *SubstrateCheckMetadataModeEnabledClient) nextNonce(meta *types.Metadata) (types.U32, error) {
key, err := types.CreateStorageKey(meta, "System", "Account", c.key.PublicKey, nil)
if err != nil {
return 0, err
}

var latestNonce types.U32
var acct types.AccountInfo
exists, err := c.Conn.RPC.State.GetStorageLatest(key, &acct)
if err != nil {
return 0, err
}

if !exists {
latestNonce = 0
} else {
latestNonce = acct.Nonce
}

if latestNonce < c.nonce {
return c.nonce, nil
}

return latestNonce, nil
}

func (c *SubstrateCheckMetadataModeEnabledClient) submitAndWatchExtrinsic(opts types.SignatureOptions, ext *types.Extrinsic) (*author.ExtrinsicStatusSubscription, error) {
err := ext.Sign(*c.key, opts)
if err != nil {
return nil, err
}

sub, err := c.Conn.RPC.Author.SubmitAndWatchExtrinsic(*ext)
if err != nil {
return nil, err
}

return sub, nil
}

func (c *SubstrateCheckMetadataModeEnabledClient) checkExtrinsicSuccess(extHash types.Hash, blockHash types.Hash) error {
block, err := c.Conn.Chain.GetBlock(blockHash)
if err != nil {
return err
}

evts, err := c.Conn.GetBlockEvents(blockHash)
if err != nil {
return err
}

for _, event := range evts {
index := event.Phase.AsApplyExtrinsic
hash, err := ExtrinsicHashCheckMetadataModeEnabled(block.Block.Extrinsics[index])
if err != nil {
return err
}

if extHash != hash {
continue
}

if event.Name == events.ExtrinsicFailedEvent {
return fmt.Errorf("extrinsic failed")
}
if event.Name == events.FailedHandlerExecutionEvent {
return fmt.Errorf("extrinsic failed with failed handler execution")
}
if event.Name == events.ExtrinsicSuccessEvent {
return nil
}
}

return fmt.Errorf("no event found")
}

func (c *SubstrateCheckMetadataModeEnabledClient) LatestBlock() (*big.Int, error) {
block, err := c.Conn.Chain.GetBlockLatest()
if err != nil {
return nil, err
}
return big.NewInt(int64(block.Block.Header.Number)), nil
}

func ExtrinsicHashCheckMetadataModeEnabled(ext types.Extrinsic) (types.Hash, error) {
extHash := bytes.NewBuffer([]byte{})
encoder := scale.NewEncoder(extHash)
err := ext.Encode(*encoder)
if err != nil {
return types.Hash{}, err
}
return types.NewHash(extHash.Bytes()), nil
}
106 changes: 106 additions & 0 deletions chains/substrate/connection/check_metadata_hash_enabled_connection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// The Licensed Work is (c) 2022 Sygma
// SPDX-License-Identifier: LGPL-3.0-only

package connection

import (
"math/big"
"sync"

"github.com/sygmaprotocol/go-substrate-rpc-client/v4/client"
"github.com/sygmaprotocol/go-substrate-rpc-client/v4/registry/parser"
"github.com/sygmaprotocol/go-substrate-rpc-client/v4/registry/retriever"
"github.com/sygmaprotocol/go-substrate-rpc-client/v4/registry/state"

"github.com/sygmaprotocol/go-substrate-rpc-client/v4/rpc"
"github.com/sygmaprotocol/go-substrate-rpc-client/v4/rpc/chain"
"github.com/sygmaprotocol/go-substrate-rpc-client/v4/types"
)

type CheckMetadataModeEnabledConnection struct {
chain.Chain
client.Client
*rpc.RPC
meta types.Metadata // Latest chain metadata
metaLock sync.RWMutex // Lock metadata for updates, allows concurrent reads
GenesisHash types.Hash // Chain genesis hash
}

func NewCheckMetadataModeEnabledConnection(url string) (*CheckMetadataModeEnabledConnection, error) {
client, err := client.Connect(url)
if err != nil {
return nil, err
}
rpc, err := rpc.NewRPC(client)
if err != nil {
return nil, err
}

meta, err := rpc.State.GetMetadataLatest()
if err != nil {
return nil, err
}
genesisHash, err := rpc.Chain.GetBlockHash(0)
if err != nil {
return nil, err
}

return &CheckMetadataModeEnabledConnection{
meta: *meta,

RPC: rpc,
Chain: rpc.Chain,
Client: client,
GenesisHash: types.Hash(genesisHash),
}, nil
}

func (c *CheckMetadataModeEnabledConnection) GetMetadata() (meta types.Metadata) {
c.metaLock.RLock()
meta = c.meta
c.metaLock.RUnlock()
return meta
}

func (c *CheckMetadataModeEnabledConnection) UpdateMetatdata() error {
c.metaLock.Lock()
meta, err := c.RPC.State.GetMetadataLatest()
if err != nil {
c.metaLock.Unlock()
return err
}
c.meta = *meta
c.metaLock.Unlock()
return nil
}

func (c *CheckMetadataModeEnabledConnection) GetBlockEvents(hash types.Hash) ([]*parser.Event, error) {
provider := state.NewEventProvider(c.State)
eventRetriever, err := retriever.NewDefaultEventRetriever(provider, c.State)
if err != nil {
return nil, err
}

evts, err := eventRetriever.GetEvents(hash)
if err != nil {
return nil, err
}
return evts, nil
}

func (c *CheckMetadataModeEnabledConnection) FetchEvents(startBlock, endBlock *big.Int) ([]*parser.Event, error) {
evts := make([]*parser.Event, 0)
for i := new(big.Int).Set(startBlock); i.Cmp(endBlock) <= 0; i.Add(i, big.NewInt(1)) {
hash, err := c.GetBlockHash(i.Uint64())
if err != nil {
return nil, err
}

evt, err := c.GetBlockEvents(hash)
if err != nil {
return nil, err
}
evts = append(evts, evt...)
}
return evts, nil
}
9 changes: 6 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
module github.com/sygmaprotocol/sygma-core

go 1.19
go 1.21

toolchain go1.22.2

require (
github.com/centrifuge/go-substrate-rpc-client/v4 v4.1.0
github.com/centrifuge/go-substrate-rpc-client/v4 v4.2.1
github.com/ethereum/go-ethereum v1.13.2
github.com/imdario/mergo v0.3.12
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.25.0
github.com/stretchr/testify v1.8.3
github.com/sygmaprotocol/go-substrate-rpc-client/v4 v4.2.2-0.20240813185906-0395c914c6f8
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a
go.opentelemetry.io/otel v1.16.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.39.0
Expand Down Expand Up @@ -45,7 +48,7 @@ require (
github.com/rs/cors v1.8.2 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/supranational/blst v0.3.11 // indirect
github.com/vedhavyas/go-subkey v1.0.4 // indirect
github.com/vedhavyas/go-subkey/v2 v2.0.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
Expand Down
Loading

0 comments on commit e626c7d

Please sign in to comment.