From 1fd84221f524007f2bd37b9256449b580b104fcd Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Sun, 29 Sep 2024 12:36:39 +0100 Subject: [PATCH] clients/go: use existing RPC connection to retrieve calldata public key adds tests for performing a value transfer (doesn't check if encryption works) --- .github/workflows/ci-test-go.yaml | 43 +++++++++++++++++++++ .github/workflows/ci-test.yaml | 17 --------- clients/go/cipher.go | 41 +++----------------- clients/go/compat.go | 6 +-- clients/go/compat_test.go | 62 +++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/ci-test-go.yaml diff --git a/.github/workflows/ci-test-go.yaml b/.github/workflows/ci-test-go.yaml new file mode 100644 index 00000000..adea768d --- /dev/null +++ b/.github/workflows/ci-test-go.yaml @@ -0,0 +1,43 @@ +name: ci-test + +on: + push: + branches: + - main + - stable/* + - rc/* + pull_request: + branches: + - main + - stable/* + - rc/* + +jobs: + test-client-go: + name: test-client-go + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./clients/go + services: + sapphire-localnet-ci: + image: ghcr.io/oasisprotocol/sapphire-localnet:latest + ports: + - 8545:8545 + - 8546:8546 + env: + OASIS_DEPOSIT_BINARY: /oasis-deposit -test-mnemonic -n 2 + options: >- + --rm + --health-cmd="test -f /CONTAINER_READY" + --health-start-period=90s + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.22.x" + + - name: Test + run: go test -v ./... diff --git a/.github/workflows/ci-test.yaml b/.github/workflows/ci-test.yaml index eb399cb5..a6dd6a8f 100644 --- a/.github/workflows/ci-test.yaml +++ b/.github/workflows/ci-test.yaml @@ -86,20 +86,3 @@ jobs: - name: Test JS client run: pnpm test:unit - - test-client-go: - name: test-client-go - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./clients/go - steps: - - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: "1.22.x" - - - name: Test - run: go test -v ./... diff --git a/clients/go/cipher.go b/clients/go/cipher.go index 9a925635..0d4ebd98 100644 --- a/clients/go/cipher.go +++ b/clients/go/cipher.go @@ -1,16 +1,14 @@ package sapphire import ( - "bytes" - "context" "crypto/cipher" "crypto/rand" "encoding/json" "errors" "fmt" - "net/http" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/ethclient" "github.com/oasisprotocol/curve25519-voi/primitives/x25519" "github.com/oasisprotocol/deoxysii" @@ -262,44 +260,17 @@ func (c X25519DeoxysIICipher) DecryptEncoded(response []byte) ([]byte, error) { } // GetRuntimePublicKey fetches the runtime calldata public key from the default Sapphire gateway. -func GetRuntimePublicKey(chainID uint64) (*x25519.PublicKey, uint64, error) { - network, exists := Networks[chainID] - if !exists { - return nil, 0, fmt.Errorf("could not fetch public key for network with chain id %d", chainID) - } - request := Request{ - Version: "2.0", - Method: "oasis_callDataPublicKey", - ID: 1, - } - rawReq, _ := json.Marshal(request) - - req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, network.DefaultGateway, bytes.NewBuffer(rawReq)) - if err != nil { - return nil, 0, fmt.Errorf("failed to create request for runtime calldata public key: %w", err) - } - req.Header.Set("Content-Type", "application/json") - - client := http.Client{} - res, err := client.Do(req) - if err != nil { - return nil, 0, fmt.Errorf("failed to request runtime calldata public key: %w", err) - } - - decoder := json.NewDecoder(res.Body) - rpcRes := new(Response) - if err := decoder.Decode(&rpcRes); err != nil { - return nil, 0, fmt.Errorf("unexpected response to request for runtime calldata public key: %w", err) - } - res.Body.Close() - +func GetRuntimePublicKey(c *ethclient.Client) (*x25519.PublicKey, uint64, error) { var pubKey CallDataPublicKey - if err := json.Unmarshal(rpcRes.Result, &pubKey); err != nil { + + if err := c.Client().Call(&pubKey, "oasis_callDataPublicKey"); err != nil { return nil, 0, fmt.Errorf("invalid response when fetching runtime calldata public key: %w", err) } + if len(pubKey.PublicKey) != x25519.PublicKeySize { return nil, 0, fmt.Errorf("invalid public key length") } + return (*x25519.PublicKey)(pubKey.PublicKey), pubKey.Epoch, nil } diff --git a/clients/go/compat.go b/clients/go/compat.go index 5790c28d..f9bbb143 100644 --- a/clients/go/compat.go +++ b/clients/go/compat.go @@ -129,8 +129,8 @@ type WrappedBackend struct { // // If you use cipher over a longer period of time, you should create a new // cipher instance every epoch to refresh the ParaTime's ephemeral key! -func NewCipher(chainID uint64) (Cipher, error) { - runtimePublicKey, epoch, err := GetRuntimePublicKey(chainID) +func NewCipher(c *ethclient.Client) (Cipher, error) { + runtimePublicKey, epoch, err := GetRuntimePublicKey(c) if err != nil { return nil, fmt.Errorf("failed to fetch runtime callata public key: %w", err) } @@ -151,7 +151,7 @@ func WrapClient(c *ethclient.Client, sign SignerFn) (*WrappedBackend, error) { if err != nil { return nil, fmt.Errorf("failed to fetch chain ID: %w", err) } - cipher, err := NewCipher(chainID.Uint64()) + cipher, err := NewCipher(c) if err != nil { return nil, err } diff --git a/clients/go/compat_test.go b/clients/go/compat_test.go index e1cfe48e..102e0f9b 100644 --- a/clients/go/compat_test.go +++ b/clients/go/compat_test.go @@ -1,18 +1,24 @@ package sapphire import ( + "context" "encoding/base64" "encoding/json" + "log" "math/big" "testing" "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/oasisprotocol/oasis-core/go/common/cbor" "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/evm" + + "github.com/ethereum/go-ethereum/ethclient" ) func TestPackSignedCall(t *testing.T) { @@ -89,3 +95,59 @@ func TestPackSignedCall(t *testing.T) { t.Fatalf("err innerdata leash mismatch: expected %s got %s", leashOrig, leashDecoded) } } + +func TestDial(t *testing.T) { + key, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") + if err != nil { + log.Fatal(err) + } + addr1 := crypto.PubkeyToAddress(key.PublicKey) + + key2, err := crypto.GenerateKey() + if err != nil { + log.Fatal(err) + } + addr2 := crypto.PubkeyToAddress(key2.PublicKey) + + SapphireChainID := uint64(0x5afd) + client, _ := ethclient.Dial(Networks[SapphireChainID].DefaultGateway) + backend, err := WrapClient(client, func(digest [32]byte) ([]byte, error) { + // Pass in a custom signing function to interact with the signer + return crypto.Sign(digest[:], key) + }) + if err != nil { + t.Fatalf("failed to wrap client %v", err) + } + + nonce, err := client.PendingNonceAt(context.Background(), addr1) + if err != nil { + t.Fatalf("failed to get pending nonce %v", err) + } + + gasPrice, err := client.SuggestGasPrice(context.Background()) + if err != nil { + t.Fatalf("failed to get gas price %v", err) + } + + txOpts := backend.Transactor(addr1) + + tx := types.NewTransaction(nonce, addr2, big.NewInt(1), uint64(100000), gasPrice, nil) + signedTx, err := txOpts.Signer(addr1, tx) + if err != nil { + t.Fatalf("failed to sign transaction %v", err) + } + + err = client.SendTransaction(context.Background(), signedTx) + if err != nil { + t.Fatalf("failed to send transaction %v", err) + } + + receipt, err := bind.WaitMined(context.Background(), client, signedTx) + if err != nil { + t.Fatalf("transaction failed! %v", err) + } + + if receipt.Status != uint64(1) { + t.Fatalf("transaction failed! (status=%v)", receipt.Status) + } +}