diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 07ce5b0e72..49d0b2940f 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -13,7 +13,7 @@ jobs: Lint: name: Lint runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/tokenvm-load-tests.yml b/.github/workflows/tokenvm-load-tests.yml index dae0dd19da..0400710c03 100644 --- a/.github/workflows/tokenvm-load-tests.yml +++ b/.github/workflows/tokenvm-load-tests.yml @@ -10,19 +10,18 @@ on: pull_request: jobs: - tests: + cond-tests: + environment: 'long-ci' + if: ${{ github.ref != 'refs/heads/main'}} + strategy: + matrix: + level: [v1, v2, v3] # v4 is not supported runs-on: - labels: ubuntu-latest-16-cores - timeout-minutes: 30 + labels: ubuntu-20.04-32 + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v3 - # - name: Install fio - # run: sudo apt-get -y install fio - # - name: Run disk tests - # working-directory: ./examples/tokenvm - # shell: bash - # run: scripts/tests.disk.sh - name: Set up Go uses: actions/setup-go@v3 with: @@ -31,7 +30,27 @@ jobs: - name: Run load tests working-directory: ./examples/tokenvm shell: bash - run: scripts/tests.load.sh + run: GOAMD64=${{ matrix.level }} scripts/tests.load.sh + main-tests: + if: ${{ github.ref == 'refs/heads/main'}} + strategy: + matrix: + level: [v1, v2, v3] # v4 is not supported + runs-on: + labels: ubuntu-20.04-32 + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: "1.20" + check-latest: true + - name: Run load tests + working-directory: ./examples/tokenvm + shell: bash + run: GOAMD64=${{ matrix.level }} scripts/tests.load.sh concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} diff --git a/.github/workflows/tokenvm-release.yml b/.github/workflows/tokenvm-release.yml index 38148d29c8..1259626d56 100644 --- a/.github/workflows/tokenvm-release.yml +++ b/.github/workflows/tokenvm-release.yml @@ -12,9 +12,56 @@ on: pull_request: jobs: - release: + cond-release: # We build with 20.04 to maintain max compatibility: https://github.com/golang/go/issues/57328 - runs-on: ubuntu-20.04-16-cores + runs-on: ubuntu-20.04-32 + environment: 'long-ci' + if: ${{ github.ref != 'refs/heads/main'}} + steps: + - name: Git checkout + uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: "1.20" + check-latest: true + - name: Set up arm64 cross compiler + run: sudo apt-get -y install gcc-aarch64-linux-gnu + - name: Checkout osxcross + uses: actions/checkout@v2 + with: + repository: tpoechtrager/osxcross + path: osxcross + - name: Build osxcross + run: | + sudo apt-get -y install clang llvm-dev libxml2-dev uuid-dev libssl-dev bash patch make tar xz-utils bzip2 gzip sed cpio libbz2-dev + cd osxcross + wget https://github.com/joseluisq/macosx-sdks/releases/download/12.3/$MACOS_SDK_FNAME -O tarballs/$MACOS_SDK_FNAME + echo $MACOS_SDK_CHECKSUM tarballs/$MACOS_SDK_FNAME | sha256sum -c - + UNATTENDED=1 ./build.sh + echo $PWD/target/bin >> $GITHUB_PATH + env: + MACOS_SDK_FNAME: MacOSX12.3.sdk.tar.xz + MACOS_SDK_CHECKSUM: 3abd261ceb483c44295a6623fdffe5d44fc4ac2c872526576ec5ab5ad0f6e26c + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + distribution: goreleaser + version: latest + args: release + workdir: ./examples/tokenvm/ + env: + # https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Archive Builds + uses: actions/upload-artifact@v3 + with: + name: dist + path: ./examples/tokenvm/dist + main-release: + # We build with 20.04 to maintain max compatibility: https://github.com/golang/go/issues/57328 + runs-on: ubuntu-20.04-32 + if: ${{ github.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/tags/v')}} steps: - name: Git checkout uses: actions/checkout@v3 @@ -56,7 +103,6 @@ jobs: with: name: dist path: ./examples/tokenvm/dist - concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} diff --git a/.github/workflows/tokenvm-static-analysis.yml b/.github/workflows/tokenvm-static-analysis.yml index e4bc745ed5..d00d9c3a46 100644 --- a/.github/workflows/tokenvm-static-analysis.yml +++ b/.github/workflows/tokenvm-static-analysis.yml @@ -13,7 +13,7 @@ jobs: Lint: name: Lint runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/tokenvm-sync-tests.yml b/.github/workflows/tokenvm-sync-tests.yml index 19e1771ea0..36165bb6e5 100644 --- a/.github/workflows/tokenvm-sync-tests.yml +++ b/.github/workflows/tokenvm-sync-tests.yml @@ -10,10 +10,31 @@ on: pull_request: jobs: - tests: + cond-tests: + environment: 'long-ci' + if: ${{ github.ref != 'refs/heads/main'}} runs-on: - labels: ubuntu-latest-16-cores - timeout-minutes: 45 + labels: ubuntu-20.04-32 + timeout-minutes: 25 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: "1.20" + check-latest: true + - name: Run sync tests + working-directory: ./examples/tokenvm + shell: bash + run: scripts/run.sh + env: + MODE: "full-test" + main-tests: + if: ${{ github.ref == 'refs/heads/main'}} + runs-on: + labels: ubuntu-20.04-32 + timeout-minutes: 25 steps: - name: Checkout uses: actions/checkout@v3 @@ -28,7 +49,6 @@ jobs: run: scripts/run.sh env: MODE: "full-test" - concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true diff --git a/.github/workflows/tokenvm-unit-tests.yml b/.github/workflows/tokenvm-unit-tests.yml index 4d83ffa99b..c71f3bf5ec 100644 --- a/.github/workflows/tokenvm-unit-tests.yml +++ b/.github/workflows/tokenvm-unit-tests.yml @@ -12,8 +12,8 @@ on: jobs: tests: runs-on: - labels: ubuntu-latest-16-cores - timeout-minutes: 30 + labels: ubuntu-20.04-32 + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 9cc3c825f1..f817d987ac 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -13,7 +13,7 @@ jobs: tests: name: Unit Tests runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v3 diff --git a/README.md b/README.md index fca4d5fc4b..893e2eac55 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ +

--- diff --git a/chain/block.go b/chain/block.go index e205beda80..c2954270ca 100644 --- a/chain/block.go +++ b/chain/block.go @@ -544,11 +544,13 @@ func (b *StatelessBlock) innerVerify(ctx context.Context) (merkledb.TrieView, er processor.Prefetch(ctx, state) // Process new transactions - unitsConsumed, surplusFee, results, err := processor.Execute(ctx, ectx, r) + unitsConsumed, surplusFee, results, stateChanges, stateOps, err := processor.Execute(ctx, ectx, r) if err != nil { log.Error("failed to execute block", zap.Error(err)) return nil, err } + b.vm.RecordStateChanges(stateChanges) + b.vm.RecordStateOperations(stateOps) b.results = results if b.UnitsConsumed != unitsConsumed { return nil, fmt.Errorf( diff --git a/chain/builder.go b/chain/builder.go index 02b13f4dfa..6e29432022 100644 --- a/chain/builder.go +++ b/chain/builder.go @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/avalanchego/ids" smblock "github.com/ava-labs/avalanchego/snow/engine/snowman/block" + "github.com/ava-labs/avalanchego/utils/math" "go.opentelemetry.io/otel/attribute" "go.uber.org/zap" @@ -66,12 +67,13 @@ func BuildBlock( } b := NewBlock(ectx, vm, parent, nextTime) - state, err := parent.childState(ctx, r.GetMaxBlockTxs()) + changesEstimate := math.Min(vm.Mempool().Len(ctx), r.GetMaxBlockTxs()) + state, err := parent.childState(ctx, changesEstimate) if err != nil { log.Warn("block building failed: couldn't get parent db", zap.Error(err)) return nil, err } - ts := tstate.New(r.GetMaxBlockTxs(), r.GetMaxBlockTxs()) + ts := tstate.New(changesEstimate) // Restorable txs after block attempt finishes b.Txs = []*Transaction{} @@ -155,7 +157,7 @@ func BuildBlock( // TODO: prefetch state of upcoming txs that we will pull (should make much // faster) txStart := ts.OpIndex() - if err := ts.FetchAndSetScope(ctx, state, next.StateKeys(sm)); err != nil { + if err := ts.FetchAndSetScope(ctx, next.StateKeys(sm), state); err != nil { return false, true, false, err } @@ -280,6 +282,8 @@ func BuildBlock( zap.Int("mempool size", b.vm.Mempool().Len(ctx)), zap.Duration("mempool lock wait", lockWait), zap.Bool("context", blockContext != nil), + zap.Int("state changes", ts.PendingChanges()), + zap.Int("state operations", ts.OpIndex()), ) return b, nil } diff --git a/chain/dependencies.go b/chain/dependencies.go index 24b9d30356..e541591a1e 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -64,12 +64,13 @@ type VM interface { UpdateSyncTarget(*StatelessBlock) (bool, error) StateReady() bool - // Record the duration of various operations to populate chain metrics + // Collect useful metrics // - // If there was a long-lived [Chain] struct, we would store metrics for chain - // there. - RecordRootCalculated(t time.Duration) // only called in Verify - RecordWaitSignatures(t time.Duration) // only called in Verify + // TODO: break out into own interface + RecordRootCalculated(time.Duration) // only called in Verify + RecordWaitSignatures(time.Duration) // only called in Verify + RecordStateChanges(int) + RecordStateOperations(int) } type Mempool interface { diff --git a/chain/processor.go b/chain/processor.go index 8a56dc4448..0db5af72e0 100644 --- a/chain/processor.go +++ b/chain/processor.go @@ -9,11 +9,15 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/trace" - "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/hypersdk/tstate" ) +type fetchData struct { + v []byte + exists bool +} + type txData struct { tx *Transaction storage map[string][]byte @@ -45,21 +49,25 @@ func (p *Processor) Prefetch(ctx context.Context, db Database) { defer span.End() // Store required keys for each set - alreadyFetched := set.Set[string]{} + alreadyFetched := make(map[string]*fetchData, len(p.blk.GetTxs())) for _, tx := range p.blk.GetTxs() { storage := map[string][]byte{} for _, k := range tx.StateKeys(sm) { sk := string(k) - if alreadyFetched.Contains(sk) { + if v, ok := alreadyFetched[sk]; ok { + if v.exists { + storage[sk] = v.v + } continue } v, err := db.GetValue(ctx, k) if errors.Is(err, database.ErrNotFound) { + alreadyFetched[sk] = &fetchData{nil, false} continue } else if err != nil { panic(err) } - alreadyFetched.Add(sk) + alreadyFetched[sk] = &fetchData{v, true} storage[sk] = v } p.readyTxs <- &txData{tx, storage} @@ -74,14 +82,14 @@ func (p *Processor) Execute( ctx context.Context, ectx *ExecutionContext, r Rules, -) (uint64, uint64, []*Result, error) { +) (uint64, uint64, []*Result, int, int, error) { ctx, span := p.tracer.Start(ctx, "Processor.Execute") defer span.End() var ( unitsConsumed = uint64(0) surplusFee = uint64(0) - ts = tstate.New(len(p.blk.Txs)*2, len(p.blk.Txs)*2) // TODO: tune this heuristic + ts = tstate.New(len(p.blk.Txs) * 2) // TODO: tune this heuristic t = p.blk.GetTimestamp() blkUnitPrice = p.blk.GetUnitPrice() results = []*Result{} @@ -90,17 +98,13 @@ func (p *Processor) Execute( for txData := range p.readyTxs { tx := txData.tx - // Update ts - for k, v := range txData.storage { - ts.SetStorage(ctx, []byte(k), v) - } // It is critical we explicitly set the scope before each transaction is // processed - ts.SetScope(ctx, tx.StateKeys(sm)) + ts.SetScope(ctx, tx.StateKeys(sm), txData.storage) // Execute tx if err := tx.PreExecute(ctx, ectx, r, ts, t); err != nil { - return 0, 0, nil, err + return 0, 0, nil, 0, 0, err } // Wait to execute transaction until we have the warp result processed. // @@ -112,12 +116,12 @@ func (p *Processor) Execute( select { case warpVerified = <-warpMsg.verifiedChan: case <-ctx.Done(): - return 0, 0, nil, ctx.Err() + return 0, 0, nil, 0, 0, ctx.Err() } } result, err := tx.Execute(ctx, ectx, r, sm, ts, t, ok && warpVerified) if err != nil { - return 0, 0, nil, err + return 0, 0, nil, 0, 0, err } surplusFee += (tx.Base.UnitPrice - blkUnitPrice) * result.Units results = append(results, result) @@ -126,9 +130,12 @@ func (p *Processor) Execute( unitsConsumed += result.Units if unitsConsumed > r.GetMaxBlockUnits() { // Exit as soon as we hit our max - return 0, 0, nil, ErrBlockTooBig + return 0, 0, nil, 0, 0, ErrBlockTooBig } } // Wait until end to write changes to avoid conflicting with pre-fetching - return unitsConsumed, surplusFee, results, ts.WriteChanges(ctx, p.db, p.tracer) + if err := ts.WriteChanges(ctx, p.db, p.tracer); err != nil { + return 0, 0, nil, 0, 0, err + } + return unitsConsumed, surplusFee, results, ts.PendingChanges(), ts.OpIndex(), nil } diff --git a/chain/transaction.go b/chain/transaction.go index 8d98218924..f66aa652d6 100644 --- a/chain/transaction.go +++ b/chain/transaction.go @@ -41,7 +41,8 @@ type Transaction struct { // all warp messages from a single source have some unique field that // prevents duplicates (like txID). We will not allow 2 instances of the same // warpID from the same sourceChainID to be accepted. - warpID ids.ID + warpID ids.ID + stateKeys [][]byte } type WarpResult struct { @@ -126,12 +127,18 @@ func (t *Transaction) UnitPrice() uint64 { return t.Base.UnitPrice } // It is ok to have duplicate ReadKeys...the processor will skip them func (t *Transaction) StateKeys(stateMapping StateManager) [][]byte { + // We assume that any transaction must modify some state key (at least to pay + // fees) + if len(t.stateKeys) != 0 { + return t.stateKeys + } keys := append(t.Action.StateKeys(t.Auth, t.ID()), t.Auth.StateKeys()...) if t.WarpMessage != nil { keys = append(keys, stateMapping.IncomingWarpKey(t.WarpMessage.SourceChainID, t.warpID)) } // Always assume a message could export a warp message keys = append(keys, stateMapping.OutgoingWarpKey(t.id)) + t.stateKeys = keys return keys } diff --git a/chain/warp_signature.go b/chain/warp_signature.go new file mode 100644 index 0000000000..b499f70046 --- /dev/null +++ b/chain/warp_signature.go @@ -0,0 +1,13 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package chain + +type WarpSignature struct { + PublicKey []byte `json:"publicKey"` + Signature []byte `json:"signature"` +} + +func NewWarpSignature(pk []byte, sig []byte) *WarpSignature { + return &WarpSignature{pk, sig} +} diff --git a/client/client.go b/client/client.go deleted file mode 100644 index a7f245dd3f..0000000000 --- a/client/client.go +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package client - -import ( - "context" - "fmt" - "time" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/validators" - "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/vms/platformvm/warp" - - "github.com/ava-labs/hypersdk/requester" - "github.com/ava-labs/hypersdk/vm" -) - -const suggestedFeeCacheRefresh = 10 * time.Second - -type Client struct { - Requester *requester.EndpointRequester - - networkID uint32 - subnetID ids.ID - chainID ids.ID - - lastSuggestedFee time.Time - unitPrice uint64 - blockCost uint64 -} - -// New creates a new client object. -func New(name string, uri string) *Client { - req := requester.New( - fmt.Sprintf("%s%s", uri, vm.Endpoint), - name, - ) - return &Client{Requester: req} -} - -func (cli *Client) Ping(ctx context.Context) (bool, error) { - resp := new(vm.PingReply) - err := cli.Requester.SendRequest(ctx, - "ping", - nil, - resp, - ) - return resp.Success, err -} - -func (cli *Client) Network(ctx context.Context) (uint32, ids.ID, ids.ID, error) { - if cli.chainID != ids.Empty { - return cli.networkID, cli.subnetID, cli.chainID, nil - } - - resp := new(vm.NetworkReply) - err := cli.Requester.SendRequest( - ctx, - "network", - nil, - resp, - ) - if err != nil { - return 0, ids.Empty, ids.Empty, err - } - cli.networkID = resp.NetworkID - cli.subnetID = resp.SubnetID - cli.chainID = resp.ChainID - return resp.NetworkID, resp.SubnetID, resp.ChainID, nil -} - -func (cli *Client) Accepted(ctx context.Context) (ids.ID, uint64, int64, error) { - resp := new(vm.LastAcceptedReply) - err := cli.Requester.SendRequest( - ctx, - "lastAccepted", - nil, - resp, - ) - return resp.BlockID, resp.Height, resp.Timestamp, err -} - -func (cli *Client) SuggestedRawFee(ctx context.Context) (uint64, uint64, error) { - if time.Since(cli.lastSuggestedFee) < suggestedFeeCacheRefresh { - return cli.unitPrice, cli.blockCost, nil - } - - resp := new(vm.SuggestedRawFeeReply) - err := cli.Requester.SendRequest( - ctx, - "suggestedRawFee", - nil, - resp, - ) - if err != nil { - return 0, 0, err - } - cli.unitPrice = resp.UnitPrice - cli.blockCost = resp.BlockCost - // We update the time last in case there are concurrent requests being - // processed (we don't want them to get an inconsistent view). - cli.lastSuggestedFee = time.Now() - return resp.UnitPrice, resp.BlockCost, nil -} - -func (cli *Client) SubmitTx(ctx context.Context, d []byte) (ids.ID, error) { - resp := new(vm.SubmitTxReply) - err := cli.Requester.SendRequest( - ctx, - "submitTx", - &vm.SubmitTxArgs{Tx: d}, - resp, - ) - return resp.TxID, err -} - -func (cli *Client) StreamingPort(ctx context.Context) (uint16, error) { - resp := new(vm.PortReply) - err := cli.Requester.SendRequest( - ctx, - "streamingPort", - nil, - resp, - ) - return resp.Port, err -} - -func (cli *Client) GetWarpSignatures( - ctx context.Context, - txID ids.ID, -) (*warp.UnsignedMessage, map[ids.NodeID]*validators.GetValidatorOutput, []*vm.WarpSignature, error) { - resp := new(vm.GetWarpSignaturesReply) - if err := cli.Requester.SendRequest( - ctx, - "getWarpSignatures", - &vm.GetWarpSignaturesArgs{TxID: txID}, - resp, - ); err != nil { - return nil, nil, nil, err - } - // Ensure message is initialized - if err := resp.Message.Initialize(); err != nil { - return nil, nil, nil, err - } - m := map[ids.NodeID]*validators.GetValidatorOutput{} - for _, vdr := range resp.Validators { - vout := &validators.GetValidatorOutput{ - NodeID: vdr.NodeID, - Weight: vdr.Weight, - } - if len(vdr.PublicKey) > 0 { - pk, err := bls.PublicKeyFromBytes(vdr.PublicKey) - if err != nil { - return nil, nil, nil, err - } - vout.PublicKey = pk - } - m[vdr.NodeID] = vout - } - return resp.Message, m, resp.Signatures, nil -} diff --git a/config/config.go b/config/config.go index 5e9042b083..dbbf2a4c34 100644 --- a/config/config.go +++ b/config/config.go @@ -32,7 +32,6 @@ func (c *Config) GetMempoolSize() int { return 2_048 } func (c *Config) GetMempoolPayerSize() int { return 32 } func (c *Config) GetMempoolExemptPayers() [][]byte { return nil } func (c *Config) GetMempoolVerifyBalances() bool { return true } -func (c *Config) GetStreamingPort() uint16 { return 0 } // auto-assigned func (c *Config) GetStreamingBacklogSize() int { return 1024 } func (c *Config) GetStateHistoryLength() int { return 256 } func (c *Config) GetStateCacheSize() int { return 65_536 } // nodes diff --git a/examples/tokenvm/client/client.go b/examples/tokenvm/client/client.go deleted file mode 100644 index fd2160d269..0000000000 --- a/examples/tokenvm/client/client.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package client - -import ( - "context" - "strings" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/hypersdk/client" - - "github.com/ava-labs/hypersdk/examples/tokenvm/consts" - "github.com/ava-labs/hypersdk/examples/tokenvm/controller" - "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" -) - -type Client struct { - *client.Client // embed standard functionality - - g *genesis.Genesis -} - -// New creates a new client object. -func New(uri string) *Client { - return &Client{Client: client.New(consts.Name, uri)} -} - -func (cli *Client) Genesis(ctx context.Context) (*genesis.Genesis, error) { - if cli.g != nil { - return cli.g, nil - } - - resp := new(controller.GenesisReply) - err := cli.Requester.SendRequest( - ctx, - "genesis", - nil, - resp, - ) - if err != nil { - return nil, err - } - cli.g = resp.Genesis - return resp.Genesis, nil -} - -func (cli *Client) Tx(ctx context.Context, id ids.ID) (bool, bool, int64, error) { - resp := new(controller.TxReply) - err := cli.Requester.SendRequest( - ctx, - "tx", - &controller.TxArgs{TxID: id}, - resp, - ) - switch { - // We use string parsing here because the JSON-RPC library we use may not - // allows us to perform errors.Is. - case err != nil && strings.Contains(err.Error(), controller.ErrTxNotFound.Error()): - return false, false, -1, nil - case err != nil: - return false, false, -1, err - } - return true, resp.Success, resp.Timestamp, nil -} - -func (cli *Client) Asset( - ctx context.Context, - asset ids.ID, -) (bool, []byte, uint64, string, bool, error) { - resp := new(controller.AssetReply) - err := cli.Requester.SendRequest( - ctx, - "asset", - &controller.AssetArgs{ - Asset: asset, - }, - resp, - ) - switch { - // We use string parsing here because the JSON-RPC library we use may not - // allows us to perform errors.Is. - case err != nil && strings.Contains(err.Error(), controller.ErrAssetNotFound.Error()): - return false, nil, 0, "", false, nil - case err != nil: - return false, nil, 0, "", false, err - } - return true, resp.Metadata, resp.Supply, resp.Owner, resp.Warp, nil -} - -func (cli *Client) Balance(ctx context.Context, addr string, asset ids.ID) (uint64, error) { - resp := new(controller.BalanceReply) - err := cli.Requester.SendRequest( - ctx, - "balance", - &controller.BalanceArgs{ - Address: addr, - Asset: asset, - }, - resp, - ) - return resp.Amount, err -} - -func (cli *Client) Orders(ctx context.Context, pair string) ([]*controller.Order, error) { - resp := new(controller.OrdersReply) - err := cli.Requester.SendRequest( - ctx, - "orders", - &controller.OrdersArgs{ - Pair: pair, - }, - resp, - ) - return resp.Orders, err -} - -func (cli *Client) Loan(ctx context.Context, asset ids.ID, destination ids.ID) (uint64, error) { - resp := new(controller.LoanReply) - err := cli.Requester.SendRequest( - ctx, - "loan", - &controller.LoanArgs{ - Asset: asset, - Destination: destination, - }, - resp, - ) - return resp.Amount, err -} diff --git a/examples/tokenvm/client/helper.go b/examples/tokenvm/client/helper.go deleted file mode 100644 index 51e312d8e8..0000000000 --- a/examples/tokenvm/client/helper.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package client - -import ( - "context" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/vms/platformvm/warp" - "github.com/ava-labs/hypersdk/chain" - "github.com/ava-labs/hypersdk/client" - "github.com/ava-labs/hypersdk/utils" -) - -func (cli *Client) GenerateTransaction( - ctx context.Context, - wm *warp.Message, - action chain.Action, - factory chain.AuthFactory, - modifiers ...client.Modifier, -) (func(context.Context) error, *chain.Transaction, uint64, error) { - // Gather chain metadata - g, err := cli.Genesis(ctx) - if err != nil { - return nil, nil, 0, err - } - _, _, chainID, err := cli.Network(ctx) // TODO: store in object to fetch less frequently - if err != nil { - return nil, nil, 0, err - } - return cli.Client.GenerateTransaction( - ctx, - &Parser{chainID, g}, - wm, - action, - factory, - modifiers...) -} - -func (cli *Client) WaitForBalance( - ctx context.Context, - addr string, - asset ids.ID, - min uint64, -) error { - return client.Wait(ctx, func(ctx context.Context) (bool, error) { - balance, err := cli.Balance(ctx, addr, asset) - if err != nil { - return false, err - } - shouldExit := balance >= min - if !shouldExit { - utils.Outf( - "{{yellow}}waiting for %s balance: %s{{/}}\n", - utils.FormatBalance(min), - addr, - ) - } - return shouldExit, nil - }) -} - -func (cli *Client) WaitForTransaction(ctx context.Context, txID ids.ID) (bool, error) { - var success bool - if err := client.Wait(ctx, func(ctx context.Context) (bool, error) { - found, isuccess, _, err := cli.Tx(ctx, txID) - if err != nil { - return false, err - } - success = isuccess - return found, nil - }); err != nil { - return false, err - } - return success, nil -} diff --git a/examples/tokenvm/client/parser.go b/examples/tokenvm/client/parser.go deleted file mode 100644 index d78652375b..0000000000 --- a/examples/tokenvm/client/parser.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package client - -import ( - "context" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/hypersdk/chain" - - "github.com/ava-labs/hypersdk/examples/tokenvm/consts" - _ "github.com/ava-labs/hypersdk/examples/tokenvm/controller" // ensure registry populated - "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" -) - -var _ chain.Parser = (*Parser)(nil) - -type Parser struct { - chainID ids.ID - genesis *genesis.Genesis -} - -func (p *Parser) ChainID() ids.ID { - return p.chainID -} - -func (p *Parser) Rules(t int64) chain.Rules { - return p.genesis.Rules(t) -} - -func (*Parser) Registry() (chain.ActionRegistry, chain.AuthRegistry) { - return consts.ActionRegistry, consts.AuthRegistry -} - -func (cli *Client) Parser(ctx context.Context) (chain.Parser, error) { - // Gather chain metadata - g, err := cli.Genesis(ctx) - if err != nil { - return nil, err - } - _, _, chainID, err := cli.Network(ctx) // TODO: store in object to fetch less frequently - if err != nil { - return nil, err - } - return &Parser{chainID, g}, nil -} diff --git a/examples/tokenvm/cmd/token-cli/cmd/action.go b/examples/tokenvm/cmd/token-cli/cmd/action.go index 038f9bfd26..768ab3f5f9 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/action.go +++ b/examples/tokenvm/cmd/token-cli/cmd/action.go @@ -16,8 +16,9 @@ import ( "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" - "github.com/ava-labs/hypersdk/examples/tokenvm/client" + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/examples/tokenvm/utils" + "github.com/ava-labs/hypersdk/rpc" hutils "github.com/ava-labs/hypersdk/utils" "github.com/manifoldco/promptui" "github.com/spf13/cobra" @@ -39,7 +40,7 @@ var transferCmd = &cobra.Command{ Use: "transfer", RunE: func(*cobra.Command, []string) error { ctx := context.Background() - _, priv, factory, cli, err := defaultActor() + _, priv, factory, cli, tcli, err := defaultActor() if err != nil { return err } @@ -49,7 +50,7 @@ var transferCmd = &cobra.Command{ if err != nil { return err } - balance, _, err := getAssetInfo(ctx, cli, priv.PublicKey(), assetID, true) + balance, _, err := getAssetInfo(ctx, tcli, priv.PublicKey(), assetID, true) if balance == 0 || err != nil { return err } @@ -73,7 +74,11 @@ var transferCmd = &cobra.Command{ } // Generate transaction - submit, tx, _, err := cli.GenerateTransaction(ctx, nil, &actions.Transfer{ + parser, err := tcli.Parser(ctx) + if err != nil { + return err + } + submit, tx, _, err := cli.GenerateTransaction(ctx, parser, nil, &actions.Transfer{ To: recipient, Asset: assetID, Value: amount, @@ -84,7 +89,7 @@ var transferCmd = &cobra.Command{ if err := submit(ctx); err != nil { return err } - success, err := cli.WaitForTransaction(ctx, tx.ID()) + success, err := tcli.WaitForTransaction(ctx, tx.ID()) if err != nil { return err } @@ -97,7 +102,7 @@ var createAssetCmd = &cobra.Command{ Use: "create-asset", RunE: func(*cobra.Command, []string) error { ctx := context.Background() - _, _, factory, cli, err := defaultActor() + _, _, factory, cli, tcli, err := defaultActor() if err != nil { return err } @@ -124,7 +129,11 @@ var createAssetCmd = &cobra.Command{ } // Generate transaction - submit, tx, _, err := cli.GenerateTransaction(ctx, nil, &actions.CreateAsset{ + parser, err := tcli.Parser(ctx) + if err != nil { + return err + } + submit, tx, _, err := cli.GenerateTransaction(ctx, parser, nil, &actions.CreateAsset{ Metadata: []byte(metadata), }, factory) if err != nil { @@ -133,7 +142,7 @@ var createAssetCmd = &cobra.Command{ if err := submit(ctx); err != nil { return err } - success, err := cli.WaitForTransaction(ctx, tx.ID()) + success, err := tcli.WaitForTransaction(ctx, tx.ID()) if err != nil { return err } @@ -146,7 +155,7 @@ var mintAssetCmd = &cobra.Command{ Use: "mint-asset", RunE: func(*cobra.Command, []string) error { ctx := context.Background() - _, priv, factory, cli, err := defaultActor() + _, priv, factory, cli, tcli, err := defaultActor() if err != nil { return err } @@ -156,7 +165,7 @@ var mintAssetCmd = &cobra.Command{ if err != nil { return err } - exists, metadata, supply, owner, warp, err := cli.Asset(ctx, assetID) + exists, metadata, supply, owner, warp, err := tcli.Asset(ctx, assetID) if err != nil { return err } @@ -200,7 +209,11 @@ var mintAssetCmd = &cobra.Command{ } // Generate transaction - submit, tx, _, err := cli.GenerateTransaction(ctx, nil, &actions.MintAsset{ + parser, err := tcli.Parser(ctx) + if err != nil { + return err + } + submit, tx, _, err := cli.GenerateTransaction(ctx, parser, nil, &actions.MintAsset{ Asset: assetID, To: recipient, Value: amount, @@ -211,7 +224,7 @@ var mintAssetCmd = &cobra.Command{ if err := submit(ctx); err != nil { return err } - success, err := cli.WaitForTransaction(ctx, tx.ID()) + success, err := tcli.WaitForTransaction(ctx, tx.ID()) if err != nil { return err } @@ -224,7 +237,7 @@ var closeOrderCmd = &cobra.Command{ Use: "close-order", RunE: func(*cobra.Command, []string) error { ctx := context.Background() - _, _, factory, cli, err := defaultActor() + _, _, factory, cli, tcli, err := defaultActor() if err != nil { return err } @@ -248,7 +261,11 @@ var closeOrderCmd = &cobra.Command{ } // Generate transaction - submit, tx, _, err := cli.GenerateTransaction(ctx, nil, &actions.CloseOrder{ + parser, err := tcli.Parser(ctx) + if err != nil { + return err + } + submit, tx, _, err := cli.GenerateTransaction(ctx, parser, nil, &actions.CloseOrder{ Order: orderID, Out: outAssetID, }, factory) @@ -258,7 +275,7 @@ var closeOrderCmd = &cobra.Command{ if err := submit(ctx); err != nil { return err } - success, err := cli.WaitForTransaction(ctx, tx.ID()) + success, err := tcli.WaitForTransaction(ctx, tx.ID()) if err != nil { return err } @@ -271,7 +288,7 @@ var createOrderCmd = &cobra.Command{ Use: "create-order", RunE: func(*cobra.Command, []string) error { ctx := context.Background() - _, priv, factory, cli, err := defaultActor() + _, priv, factory, cli, tcli, err := defaultActor() if err != nil { return err } @@ -282,7 +299,7 @@ var createOrderCmd = &cobra.Command{ return err } if inAssetID != ids.Empty { - exists, metadata, supply, _, warp, err := cli.Asset(ctx, inAssetID) + exists, metadata, supply, _, warp, err := tcli.Asset(ctx, inAssetID) if err != nil { return err } @@ -310,7 +327,7 @@ var createOrderCmd = &cobra.Command{ if err != nil { return err } - balance, _, err := getAssetInfo(ctx, cli, priv.PublicKey(), outAssetID, true) + balance, _, err := getAssetInfo(ctx, tcli, priv.PublicKey(), outAssetID, true) if balance == 0 || err != nil { return err } @@ -344,7 +361,11 @@ var createOrderCmd = &cobra.Command{ } // Generate transaction - submit, tx, _, err := cli.GenerateTransaction(ctx, nil, &actions.CreateOrder{ + parser, err := tcli.Parser(ctx) + if err != nil { + return err + } + submit, tx, _, err := cli.GenerateTransaction(ctx, parser, nil, &actions.CreateOrder{ In: inAssetID, InTick: inTick, Out: outAssetID, @@ -357,7 +378,7 @@ var createOrderCmd = &cobra.Command{ if err := submit(ctx); err != nil { return err } - success, err := cli.WaitForTransaction(ctx, tx.ID()) + success, err := tcli.WaitForTransaction(ctx, tx.ID()) if err != nil { return err } @@ -370,7 +391,7 @@ var fillOrderCmd = &cobra.Command{ Use: "fill-order", RunE: func(*cobra.Command, []string) error { ctx := context.Background() - _, priv, factory, cli, err := defaultActor() + _, priv, factory, cli, tcli, err := defaultActor() if err != nil { return err } @@ -380,7 +401,7 @@ var fillOrderCmd = &cobra.Command{ if err != nil { return err } - balance, _, err := getAssetInfo(ctx, cli, priv.PublicKey(), inAssetID, true) + balance, _, err := getAssetInfo(ctx, tcli, priv.PublicKey(), inAssetID, true) if balance == 0 || err != nil { return err } @@ -390,12 +411,12 @@ var fillOrderCmd = &cobra.Command{ if err != nil { return err } - if _, _, err := getAssetInfo(ctx, cli, priv.PublicKey(), outAssetID, false); err != nil { + if _, _, err := getAssetInfo(ctx, tcli, priv.PublicKey(), outAssetID, false); err != nil { return err } // View orders - orders, err := cli.Orders(ctx, actions.PairID(inAssetID, outAssetID)) + orders, err := tcli.Orders(ctx, actions.PairID(inAssetID, outAssetID)) if err != nil { return err } @@ -471,7 +492,11 @@ var fillOrderCmd = &cobra.Command{ if err != nil { return err } - submit, tx, _, err := cli.GenerateTransaction(ctx, nil, &actions.FillOrder{ + parser, err := tcli.Parser(ctx) + if err != nil { + return err + } + submit, tx, _, err := cli.GenerateTransaction(ctx, parser, nil, &actions.FillOrder{ Order: order.ID, Owner: owner, In: inAssetID, @@ -484,7 +509,7 @@ var fillOrderCmd = &cobra.Command{ if err := submit(ctx); err != nil { return err } - success, err := cli.WaitForTransaction(ctx, tx.ID()) + success, err := tcli.WaitForTransaction(ctx, tx.ID()) if err != nil { return err } @@ -495,8 +520,9 @@ var fillOrderCmd = &cobra.Command{ func performImport( ctx context.Context, - scli *client.Client, - dcli *client.Client, + scli *rpc.JSONRPCClient, + dcli *rpc.JSONRPCClient, + dtcli *trpc.JSONRPCClient, exportTxID ids.ID, priv crypto.PrivateKey, factory chain.AuthFactory, @@ -593,12 +619,16 @@ func performImport( } // Attempt to send dummy transaction if needed - if err := submitDummy(ctx, dcli, priv.PublicKey(), factory); err != nil { + if err := submitDummy(ctx, dcli, dtcli, priv.PublicKey(), factory); err != nil { return err } // Generate transaction - submit, tx, _, err := dcli.GenerateTransaction(ctx, msg, &actions.ImportAsset{ + parser, err := dtcli.Parser(ctx) + if err != nil { + return err + } + submit, tx, _, err := dcli.GenerateTransaction(ctx, parser, msg, &actions.ImportAsset{ Fill: fill, }, factory) if err != nil { @@ -607,7 +637,7 @@ func performImport( if err := submit(ctx); err != nil { return err } - success, err := dcli.WaitForTransaction(ctx, tx.ID()) + success, err := dtcli.WaitForTransaction(ctx, tx.ID()) if err != nil { return err } @@ -617,7 +647,8 @@ func performImport( func submitDummy( ctx context.Context, - cli *client.Client, + cli *rpc.JSONRPCClient, + tcli *trpc.JSONRPCClient, dest crypto.PublicKey, factory chain.AuthFactory, ) error { @@ -638,7 +669,11 @@ func submitDummy( ) logEmitted = true } - submit, tx, _, err := cli.GenerateTransaction(ctx, nil, &actions.Transfer{ + parser, err := tcli.Parser(ctx) + if err != nil { + return err + } + submit, tx, _, err := cli.GenerateTransaction(ctx, parser, nil, &actions.Transfer{ To: dest, Value: txsSent + 1, // prevent duplicate txs }, factory) @@ -648,7 +683,7 @@ func submitDummy( if err := submit(ctx); err != nil { return err } - if _, err := cli.WaitForTransaction(ctx, tx.ID()); err != nil { + if _, err := tcli.WaitForTransaction(ctx, tx.ID()); err != nil { return err } txsSent++ @@ -667,7 +702,7 @@ var importAssetCmd = &cobra.Command{ Use: "import-asset", RunE: func(*cobra.Command, []string) error { ctx := context.Background() - currentChainID, priv, factory, dcli, err := defaultActor() + currentChainID, priv, factory, dcli, dtcli, err := defaultActor() if err != nil { return err } @@ -677,10 +712,10 @@ var importAssetCmd = &cobra.Command{ if err != nil { return err } - scli := client.New(uris[0]) + scli := rpc.NewJSONRPCClient(uris[0]) // Perform import - return performImport(ctx, scli, dcli, ids.Empty, priv, factory) + return performImport(ctx, scli, dcli, dtcli, ids.Empty, priv, factory) }, } @@ -688,7 +723,7 @@ var exportAssetCmd = &cobra.Command{ Use: "export-asset", RunE: func(*cobra.Command, []string) error { ctx := context.Background() - currentChainID, priv, factory, cli, err := defaultActor() + currentChainID, priv, factory, cli, tcli, err := defaultActor() if err != nil { return err } @@ -698,7 +733,7 @@ var exportAssetCmd = &cobra.Command{ if err != nil { return err } - balance, sourceChainID, err := getAssetInfo(ctx, cli, priv.PublicKey(), assetID, true) + balance, sourceChainID, err := getAssetInfo(ctx, tcli, priv.PublicKey(), assetID, true) if balance == 0 || err != nil { return err } @@ -778,12 +813,16 @@ var exportAssetCmd = &cobra.Command{ } // Attempt to send dummy transaction if needed - if err := submitDummy(ctx, cli, priv.PublicKey(), factory); err != nil { + if err := submitDummy(ctx, cli, tcli, priv.PublicKey(), factory); err != nil { return err } // Generate transaction - submit, tx, _, err := cli.GenerateTransaction(ctx, nil, &actions.ExportAsset{ + parser, err := tcli.Parser(ctx) + if err != nil { + return err + } + submit, tx, _, err := cli.GenerateTransaction(ctx, parser, nil, &actions.ExportAsset{ To: recipient, Asset: assetID, Value: amount, @@ -801,7 +840,7 @@ var exportAssetCmd = &cobra.Command{ if err := submit(ctx); err != nil { return err } - success, err := cli.WaitForTransaction(ctx, tx.ID()) + success, err := tcli.WaitForTransaction(ctx, tx.ID()) if err != nil { return err } @@ -817,7 +856,7 @@ var exportAssetCmd = &cobra.Command{ if err != nil { return err } - if err := performImport(ctx, cli, client.New(uris[0]), tx.ID(), priv, factory); err != nil { + if err := performImport(ctx, cli, rpc.NewJSONRPCClient(uris[0]), trpc.NewJSONRPCClient(uris[0], destination), tx.ID(), priv, factory); err != nil { return err } } diff --git a/examples/tokenvm/cmd/token-cli/cmd/chain.go b/examples/tokenvm/cmd/token-cli/cmd/chain.go index ca685488d5..ccfbf52bf9 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/chain.go +++ b/examples/tokenvm/cmd/token-cli/cmd/chain.go @@ -16,15 +16,15 @@ import ( runner "github.com/ava-labs/avalanche-network-runner/client" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/hypersdk/rpc" "github.com/ava-labs/hypersdk/utils" - "github.com/ava-labs/hypersdk/vm" "github.com/spf13/cobra" "gopkg.in/yaml.v2" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" - "github.com/ava-labs/hypersdk/examples/tokenvm/client" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" tutils "github.com/ava-labs/hypersdk/examples/tokenvm/utils" ) @@ -208,7 +208,7 @@ var chainInfoCmd = &cobra.Command{ if err != nil { return err } - cli := client.New(uris[0]) + cli := rpc.NewJSONRPCClient(uris[0]) networkID, subnetID, chainID, err := cli.Network(context.Background()) if err != nil { return err @@ -234,22 +234,16 @@ var watchChainCmd = &cobra.Command{ if err := CloseDatabase(); err != nil { return err } - cli := client.New(uris[0]) - port, err := cli.StreamingPort(ctx) + cli := trpc.NewJSONRPCClient(uris[0], chainID) + utils.Outf("{{yellow}}uri:{{/}} %s\n", uris[0]) + scli, err := rpc.NewWebSocketClient(uris[0]) if err != nil { return err } - host, err := utils.GetHost(uris[0]) - if err != nil { - return err - } - uri := fmt.Sprintf("%s:%d", host, port) - utils.Outf("{{yellow}}uri:{{/}} %s\n", uri) - scli, err := vm.NewStreamingClient(uri) - if err != nil { + defer scli.Close() + if err := scli.RegisterBlocks(); err != nil { return err } - defer scli.Close() parser, err := cli.Parser(ctx) if err != nil { return err @@ -258,7 +252,7 @@ var watchChainCmd = &cobra.Command{ start := time.Now() utils.Outf("{{green}}watching for new blocks on %s 👀{{/}}\n", chainID) for ctx.Err() == nil { - blk, results, err := scli.ListenForBlock(parser) + blk, results, err := scli.ListenBlock(ctx, parser) if err != nil { return err } diff --git a/examples/tokenvm/cmd/token-cli/cmd/key.go b/examples/tokenvm/cmd/token-cli/cmd/key.go index a449872408..d02d998989 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/key.go +++ b/examples/tokenvm/cmd/token-cli/cmd/key.go @@ -12,7 +12,7 @@ import ( "github.com/fatih/color" "github.com/spf13/cobra" - "github.com/ava-labs/hypersdk/examples/tokenvm/client" + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/examples/tokenvm/utils" ) @@ -85,7 +85,7 @@ var setKeyCmd = &cobra.Command{ hutils.Outf("{{red}}no stored keys{{/}}\n") return nil } - _, uris, err := GetDefaultChain() + chainID, uris, err := GetDefaultChain() if err != nil { return err } @@ -93,7 +93,7 @@ var setKeyCmd = &cobra.Command{ hutils.Outf("{{red}}no available chains{{/}}\n") return nil } - cli := client.New(uris[0]) + cli := trpc.NewJSONRPCClient(uris[0], chainID) hutils.Outf("{{cyan}}stored keys:{{/}} %d\n", len(keys)) for i := 0; i < len(keys); i++ { address := utils.Address(keys[i].PublicKey()) @@ -129,7 +129,7 @@ var balanceKeyCmd = &cobra.Command{ if err != nil { return err } - _, uris, err := GetDefaultChain() + chainID, uris, err := GetDefaultChain() if err != nil { return err } @@ -145,7 +145,7 @@ var balanceKeyCmd = &cobra.Command{ } for _, uri := range uris[:max] { hutils.Outf("{{yellow}}uri:{{/}} %s\n", uri) - if _, _, err = getAssetInfo(ctx, client.New(uri), priv.PublicKey(), assetID, true); err != nil { + if _, _, err = getAssetInfo(ctx, trpc.NewJSONRPCClient(uri, chainID), priv.PublicKey(), assetID, true); err != nil { return err } } diff --git a/examples/tokenvm/cmd/token-cli/cmd/spam.go b/examples/tokenvm/cmd/token-cli/cmd/spam.go index 507f1134b5..c61b5e5ff8 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/spam.go +++ b/examples/tokenvm/cmd/token-cli/cmd/spam.go @@ -6,9 +6,7 @@ package cmd import ( "context" - "fmt" "math/rand" - "net/url" "os" "os/signal" "strings" @@ -23,19 +21,19 @@ import ( "github.com/ava-labs/hypersdk/crypto" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" - "github.com/ava-labs/hypersdk/examples/tokenvm/client" + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/examples/tokenvm/utils" - "github.com/ava-labs/hypersdk/listeners" + "github.com/ava-labs/hypersdk/rpc" hutils "github.com/ava-labs/hypersdk/utils" - "github.com/ava-labs/hypersdk/vm" "github.com/spf13/cobra" ) const feePerTx = 1000 type txIssuer struct { - c *client.Client - d *vm.Client + c *rpc.JSONRPCClient + tc *trpc.JSONRPCClient + d *rpc.WebSocketClient l sync.Mutex outstandingTxs int @@ -79,7 +77,7 @@ var runSpamCmd = &cobra.Command{ ctx := context.Background() // Select chain - _, uris, err := promptChain("select chainID", nil) + chainID, uris, err := promptChain("select chainID", nil) if err != nil { return err } @@ -92,12 +90,13 @@ var runSpamCmd = &cobra.Command{ if len(keys) == 0 { return ErrNoKeys } - cli := client.New(uris[0]) + cli := rpc.NewJSONRPCClient(uris[0]) + tcli := trpc.NewJSONRPCClient(uris[0], chainID) hutils.Outf("{{cyan}}stored keys:{{/}} %d\n", len(keys)) balances := make([]uint64, len(keys)) for i := 0; i < len(keys); i++ { address := utils.Address(keys[i].PublicKey()) - balance, err := cli.Balance(ctx, address, ids.Empty) + balance, err := tcli.Balance(ctx, address, ids.Empty) if err != nil { return err } @@ -130,20 +129,15 @@ var runSpamCmd = &cobra.Command{ assetString(ids.Empty), ) accounts := make([]crypto.PrivateKey, numAccounts) - port, err := cli.StreamingPort(ctx) + dcli, err := rpc.NewWebSocketClient(uris[0]) if err != nil { return err } - u, err := url.Parse(uris[0]) - if err != nil { - return err - } - tcpURI := fmt.Sprintf("%s:%d", u.Hostname(), port) - dcli, err := vm.NewStreamingClient(tcpURI) + funds := map[crypto.PublicKey]uint64{} + parser, err := tcli.Parser(ctx) if err != nil { return err } - funds := map[crypto.PublicKey]uint64{} for i := 0; i < numAccounts; i++ { // Create account pk, err := crypto.GeneratePrivateKey() @@ -153,7 +147,7 @@ var runSpamCmd = &cobra.Command{ accounts[i] = pk // Send funds - _, tx, _, err := cli.GenerateTransaction(ctx, nil, &actions.Transfer{ + _, tx, _, err := cli.GenerateTransaction(ctx, parser, nil, &actions.Transfer{ To: pk.PublicKey(), Asset: ids.Empty, Value: distAmount, @@ -161,7 +155,7 @@ var runSpamCmd = &cobra.Command{ if err != nil { return err } - if err := dcli.IssueTx(tx); err != nil { + if err := dcli.RegisterTx(tx); err != nil { return err } funds[pk.PublicKey()] = distAmount @@ -172,7 +166,7 @@ var runSpamCmd = &cobra.Command{ } } for i := 0; i < numAccounts; i++ { - _, dErr, result, err := dcli.ListenForTx() + _, dErr, result, err := dcli.ListenTx(ctx) if err != nil { return err } @@ -189,21 +183,13 @@ var runSpamCmd = &cobra.Command{ // Kickoff txs clients := make([]*txIssuer, len(uris)) for i := 0; i < len(uris); i++ { - c := client.New(uris[i]) - port, err := c.StreamingPort(ctx) - if err != nil { - return err - } - u, err := url.Parse(uris[i]) - if err != nil { - return err - } - tcpURI := fmt.Sprintf("%s:%d", u.Hostname(), port) - cli, err := vm.NewStreamingClient(tcpURI) + cli := rpc.NewJSONRPCClient(uris[i]) + tcli := trpc.NewJSONRPCClient(uris[i], chainID) + dcli, err := rpc.NewWebSocketClient(uris[i]) if err != nil { return err } - clients[i] = &txIssuer{c: c, d: cli} + clients[i] = &txIssuer{c: cli, tc: tcli, d: dcli} } signals := make(chan os.Signal, 2) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) @@ -227,7 +213,7 @@ var runSpamCmd = &cobra.Command{ wg.Add(1) go func() { for { - txID, dErr, result, err := issuer.d.ListenForTx() + txID, dErr, result, err := issuer.d.ListenTx(context.TODO()) if err != nil { return } @@ -246,7 +232,7 @@ var runSpamCmd = &cobra.Command{ } } else { // We can't error match here because we receive it over the wire. - if !strings.Contains(dErr.Error(), listeners.ErrExpired.Error()) { + if !strings.Contains(dErr.Error(), rpc.ErrExpired.Error()) { hutils.Outf("{{orange}}pre-execute tx failure:{{/}} %v\n", dErr) } } @@ -299,11 +285,17 @@ var runSpamCmd = &cobra.Command{ if err != nil { return err } - _, tx, fees, err = issuer.c.GenerateTransaction(ctx, nil, &actions.Transfer{ - To: recipient, - Asset: ids.Empty, - Value: 1, - }, auth.NewED25519Factory(accounts[i])) + _, tx, fees, err = issuer.c.GenerateTransaction( + ctx, + parser, + nil, + &actions.Transfer{ + To: recipient, + Asset: ids.Empty, + Value: 1, + }, + auth.NewED25519Factory(accounts[i]), + ) if err != nil { hutils.Outf("{{orange}}failed to generate:{{/}} %v\n", err) continue @@ -319,7 +311,7 @@ var runSpamCmd = &cobra.Command{ infl.Lock() inflightTxs.Add(tx.ID()) infl.Unlock() - if err := issuer.d.IssueTx(tx); err != nil { + if err := issuer.d.RegisterTx(tx); err != nil { infl.Lock() inflightTxs.Remove(tx.ID()) infl.Unlock() @@ -374,7 +366,7 @@ var runSpamCmd = &cobra.Command{ for { select { case <-t.C: - _ = submitDummy(dctx, cli, key.PublicKey(), factory) + _ = submitDummy(dctx, cli, tcli, key.PublicKey(), factory) case <-dctx.Done(): return } @@ -397,7 +389,7 @@ var runSpamCmd = &cobra.Command{ sent++ // Send funds returnAmt := balance - transferFee - _, tx, _, err := cli.GenerateTransaction(ctx, nil, &actions.Transfer{ + _, tx, _, err := cli.GenerateTransaction(ctx, parser, nil, &actions.Transfer{ To: key.PublicKey(), Asset: ids.Empty, Value: returnAmt, @@ -405,7 +397,7 @@ var runSpamCmd = &cobra.Command{ if err != nil { return err } - if err := dcli.IssueTx(tx); err != nil { + if err := dcli.RegisterTx(tx); err != nil { return err } returnedBalance += returnAmt @@ -416,7 +408,7 @@ var runSpamCmd = &cobra.Command{ } } for i := 0; i < sent; i++ { - _, dErr, result, err := dcli.ListenForTx() + _, dErr, result, err := dcli.ListenTx(context.TODO()) if err != nil { return err } diff --git a/examples/tokenvm/cmd/token-cli/cmd/utils.go b/examples/tokenvm/cmd/token-cli/cmd/utils.go index 232d95df8d..ed0d23361a 100644 --- a/examples/tokenvm/cmd/token-cli/cmd/utils.go +++ b/examples/tokenvm/cmd/token-cli/cmd/utils.go @@ -14,9 +14,10 @@ import ( hconsts "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" - "github.com/ava-labs/hypersdk/examples/tokenvm/client" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/examples/tokenvm/utils" + "github.com/ava-labs/hypersdk/rpc" hutils "github.com/ava-labs/hypersdk/utils" "github.com/manifoldco/promptui" ) @@ -348,7 +349,7 @@ func printStatus(txID ids.ID, success bool) { func getAssetInfo( ctx context.Context, - cli *client.Client, + cli *trpc.JSONRPCClient, publicKey crypto.PublicKey, assetID ids.ID, checkBalance bool, @@ -404,17 +405,24 @@ func getAssetInfo( return balance, sourceChainID, nil } -func defaultActor() (ids.ID, crypto.PrivateKey, *auth.ED25519Factory, *client.Client, error) { +func defaultActor() (ids.ID, crypto.PrivateKey, *auth.ED25519Factory, *rpc.JSONRPCClient, *trpc.JSONRPCClient, error) { priv, err := GetDefaultKey() if err != nil { - return ids.Empty, crypto.EmptyPrivateKey, nil, nil, err + return ids.Empty, crypto.EmptyPrivateKey, nil, nil, nil, err } chainID, uris, err := GetDefaultChain() if err != nil { - return ids.Empty, crypto.EmptyPrivateKey, nil, nil, err + return ids.Empty, crypto.EmptyPrivateKey, nil, nil, nil, err } // For [defaultActor], we always send requests to the first returned URI. - return chainID, priv, auth.NewED25519Factory(priv), client.New(uris[0]), nil + return chainID, priv, auth.NewED25519Factory( + priv, + ), rpc.NewJSONRPCClient( + uris[0], + ), trpc.NewJSONRPCClient( + uris[0], + chainID, + ), nil } func GetDefaultKey() (crypto.PrivateKey, error) { diff --git a/examples/tokenvm/controller/controller.go b/examples/tokenvm/controller/controller.go index b3aadaa63b..e39a2c75bb 100644 --- a/examples/tokenvm/controller/controller.go +++ b/examples/tokenvm/controller/controller.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/gossiper" "github.com/ava-labs/hypersdk/pebble" + hrpc "github.com/ava-labs/hypersdk/rpc" "github.com/ava-labs/hypersdk/utils" "github.com/ava-labs/hypersdk/vm" "go.uber.org/zap" @@ -24,8 +25,9 @@ import ( "github.com/ava-labs/hypersdk/examples/tokenvm/config" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" + "github.com/ava-labs/hypersdk/examples/tokenvm/orderbook" + "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/examples/tokenvm/storage" - tutils "github.com/ava-labs/hypersdk/examples/tokenvm/utils" "github.com/ava-labs/hypersdk/examples/tokenvm/version" ) @@ -43,7 +45,7 @@ type Controller struct { metaDB database.Database - orderBook *OrderBook + orderBook *orderbook.OrderBook } func New() *vm.VM { @@ -126,13 +128,19 @@ func (c *Controller) Initialize( } // Create handlers + // + // hypersdk handler are initiatlized automatically, you just need to + // initialize custom handlers here. apis := map[string]*common.HTTPHandler{} - endpoint, err := utils.NewHandler(consts.Name, &Handler{inner.Handler(), c}) + jsonRPCHandler, err := hrpc.NewJSONRPCHandler( + consts.Name, + rpc.NewJSONRPCServer(c), + common.NoLock, + ) if err != nil { return nil, nil, nil, nil, nil, nil, nil, nil, nil, err } - apis[vm.Endpoint] = endpoint - // TODO: add vm.WSEndpoint + apis[rpc.JSONRPCEndpoint] = jsonRPCHandler // Create builder and gossiper var ( @@ -153,7 +161,7 @@ func (c *Controller) Initialize( } // Initialize order book used to track all open orders - c.orderBook = NewOrderBook(c, c.config.TrackedPairs) + c.orderBook = orderbook.New(c, c.config.TrackedPairs) return c.config, c.genesis, build, gossip, blockDB, stateDB, apis, consts.ActionRegistry, consts.AuthRegistry, nil } @@ -199,17 +207,7 @@ func (c *Controller) Accepted(ctx context.Context, blk *chain.StatelessBlock) er case *actions.CreateOrder: c.metrics.createOrder.Inc() actor := auth.GetActor(tx.Auth) - c.orderBook.Add( - actions.PairID(action.In, action.Out), - &Order{ - tx.ID(), - tutils.Address(actor), - action.InTick, - action.OutTick, - action.Supply, - actor, - }, - ) + c.orderBook.Add(tx.ID(), actor, action) case *actions.FillOrder: c.metrics.fillOrder.Inc() orderResult, err := actions.UnmarshalOrderResult(result.Output) diff --git a/examples/tokenvm/controller/resolutions.go b/examples/tokenvm/controller/resolutions.go new file mode 100644 index 0000000000..f9915939cc --- /dev/null +++ b/examples/tokenvm/controller/resolutions.go @@ -0,0 +1,62 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package controller + +import ( + "context" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/trace" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/hypersdk/crypto" + "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" + "github.com/ava-labs/hypersdk/examples/tokenvm/orderbook" + "github.com/ava-labs/hypersdk/examples/tokenvm/storage" +) + +func (c *Controller) Genesis() *genesis.Genesis { + return c.genesis +} + +func (c *Controller) Logger() logging.Logger { + return c.inner.Logger() +} + +func (c *Controller) Tracer() trace.Tracer { + return c.inner.Tracer() +} + +func (c *Controller) GetTransaction( + ctx context.Context, + txID ids.ID, +) (bool, int64, bool, uint64, error) { + return storage.GetTransaction(ctx, c.metaDB, txID) +} + +func (c *Controller) GetAssetFromState( + ctx context.Context, + asset ids.ID, +) (bool, []byte, uint64, crypto.PublicKey, bool, error) { + return storage.GetAssetFromState(ctx, c.inner.ReadState, asset) +} + +func (c *Controller) GetBalanceFromState( + ctx context.Context, + pk crypto.PublicKey, + asset ids.ID, +) (uint64, error) { + return storage.GetBalanceFromState(ctx, c.inner.ReadState, pk, asset) +} + +func (c *Controller) Orders(pair string, limit int) []*orderbook.Order { + return c.orderBook.Orders(pair, limit) +} + +func (c *Controller) GetLoanFromState( + ctx context.Context, + asset ids.ID, + destination ids.ID, +) (uint64, error) { + return storage.GetLoanFromState(ctx, c.inner.ReadState, asset, destination) +} diff --git a/examples/tokenvm/go.mod b/examples/tokenvm/go.mod index d30fdcd858..7ff613fb02 100644 --- a/examples/tokenvm/go.mod +++ b/examples/tokenvm/go.mod @@ -3,8 +3,8 @@ module github.com/ava-labs/hypersdk/examples/tokenvm go 1.20 require ( - github.com/ava-labs/avalanche-network-runner v1.3.10-0.20230315100749-fc888ba0646f - github.com/ava-labs/avalanchego v1.9.16 + github.com/ava-labs/avalanche-network-runner v1.4.1 + github.com/ava-labs/avalanchego v1.10.0 github.com/ava-labs/hypersdk v0.0.1 github.com/fatih/color v1.13.0 github.com/manifoldco/promptui v0.9.0 @@ -17,11 +17,11 @@ require ( ) require ( - github.com/DataDog/zstd v1.4.5 // indirect + github.com/DataDog/zstd v1.5.2 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/VictoriaMetrics/fastcache v1.10.0 // indirect - github.com/ava-labs/coreth v0.11.9-rc.0 // indirect + github.com/ava-labs/coreth v0.12.0-rc.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/btcutil v1.1.3 // indirect @@ -122,7 +122,7 @@ require ( go.opentelemetry.io/otel/trace v1.11.2 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.9.0 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.3.0 // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/mod v0.6.0 // indirect diff --git a/examples/tokenvm/go.sum b/examples/tokenvm/go.sum index b0a72c9128..a9646f67d2 100644 --- a/examples/tokenvm/go.sum +++ b/examples/tokenvm/go.sum @@ -42,8 +42,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= -github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= -github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= +github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= @@ -65,12 +65,12 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/ava-labs/avalanche-network-runner v1.3.10-0.20230315100749-fc888ba0646f h1:3vgRD7mCdrtupG3n/Rn0j2bAlU2xG1zM4Ej4Ue785+c= -github.com/ava-labs/avalanche-network-runner v1.3.10-0.20230315100749-fc888ba0646f/go.mod h1:/mKIh+RWwE3jN9xkRu0gw8oL0e+c0XtBxEe88OhJqMc= -github.com/ava-labs/avalanchego v1.9.16 h1:JarxIn7gy4V9f1dBgUxubRRO6CrqY2MprOLGqEmk+Vg= -github.com/ava-labs/avalanchego v1.9.16/go.mod h1:Unm7ruhAvLSRP+7gIfwyHNf+wEehWLsFhY9yp10nDbw= -github.com/ava-labs/coreth v0.11.9-rc.0 h1:7oK5DWtvDEMKEaduw/34r5kdwUssRsFszCl1FFUvvPc= -github.com/ava-labs/coreth v0.11.9-rc.0/go.mod h1:y41I9mWK04s8oObvQeYjkdoidtPhkPqV8prRPN6zrd4= +github.com/ava-labs/avalanche-network-runner v1.4.1 h1:S4dS/Sef3O2XdN04NRO9Tk9xijYpkv1Y1Iwj8B+bH+8= +github.com/ava-labs/avalanche-network-runner v1.4.1/go.mod h1:SOUom+OuOYwm9CUev09IeG3P4JS4mh0OiwsPhfboLJ8= +github.com/ava-labs/avalanchego v1.10.0 h1:Rn6Nyd62OkzQG5QpCgtCGVXtjuiaEzxV000kqG9aUIg= +github.com/ava-labs/avalanchego v1.10.0/go.mod h1:hTaSLGN4y/EmhmYd+yjUj9Lsm00q70V78jOYDdnLrgQ= +github.com/ava-labs/coreth v0.12.0-rc.2 h1:UNyGhuC2HxZ8eCLZiZON8xRiJkNHVZ75zknu/xqkKBA= +github.com/ava-labs/coreth v0.12.0-rc.2/go.mod h1:ZGhoIZTWbIaTmzEbprXu0hLtLdoE2PSTEFnCTYr0BRk= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -650,8 +650,8 @@ go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= diff --git a/examples/tokenvm/orderbook/dependencies.go b/examples/tokenvm/orderbook/dependencies.go new file mode 100644 index 0000000000..f41ee42e3c --- /dev/null +++ b/examples/tokenvm/orderbook/dependencies.go @@ -0,0 +1,12 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package orderbook + +import ( + "github.com/ava-labs/avalanchego/utils/logging" +) + +type Controller interface { + Logger() logging.Logger +} diff --git a/examples/tokenvm/controller/order_book.go b/examples/tokenvm/orderbook/orderbook.go similarity index 81% rename from examples/tokenvm/controller/order_book.go rename to examples/tokenvm/orderbook/orderbook.go index 944fbfe6c8..f8d1cb2a01 100644 --- a/examples/tokenvm/controller/order_book.go +++ b/examples/tokenvm/orderbook/orderbook.go @@ -1,13 +1,15 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package controller +package orderbook import ( "sync" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/hypersdk/crypto" + "github.com/ava-labs/hypersdk/examples/tokenvm/actions" + "github.com/ava-labs/hypersdk/examples/tokenvm/utils" "github.com/ava-labs/hypersdk/heap" "go.uber.org/zap" ) @@ -28,7 +30,7 @@ type Order struct { } type OrderBook struct { - c *Controller + c Controller // TODO: consider capping the number of orders in each heap (need to ensure // that doing so does not make it possible to send a bunch of small, spam @@ -40,17 +42,17 @@ type OrderBook struct { trackAll bool } -func NewOrderBook(c *Controller, trackedPairs []string) *OrderBook { +func New(c Controller, trackedPairs []string) *OrderBook { m := map[string]*heap.Heap[*Order, float64]{} trackAll := false if len(trackedPairs) == 1 && trackedPairs[0] == allPairs { trackAll = true - c.inner.Logger().Info("tracking all order books") + c.Logger().Info("tracking all order books") } else { for _, pair := range trackedPairs { // We use a max heap so we return the best rates in order. m[pair] = heap.New[*Order, float64](initialPairCapacity, true) - c.inner.Logger().Info("tracking order book", zap.String("pair", pair)) + c.Logger().Info("tracking order book", zap.String("pair", pair)) } } return &OrderBook{ @@ -61,7 +63,17 @@ func NewOrderBook(c *Controller, trackedPairs []string) *OrderBook { } } -func (o *OrderBook) Add(pair string, order *Order) { +func (o *OrderBook) Add(txID ids.ID, actor crypto.PublicKey, action *actions.CreateOrder) { + pair := actions.PairID(action.In, action.Out) + order := &Order{ + txID, + utils.Address(actor), + action.InTick, + action.OutTick, + action.Supply, + actor, + } + o.l.Lock() defer o.l.Unlock() h, ok := o.orders[pair] @@ -69,7 +81,7 @@ func (o *OrderBook) Add(pair string, order *Order) { case !ok && !o.trackAll: return case !ok && o.trackAll: - o.c.inner.Logger().Info("tracking order book", zap.String("pair", pair)) + o.c.Logger().Info("tracking order book", zap.String("pair", pair)) h = heap.New[*Order, float64](initialPairCapacity, true) o.orders[pair] = h } diff --git a/examples/tokenvm/controller/registry.go b/examples/tokenvm/registry/registry.go similarity index 99% rename from examples/tokenvm/controller/registry.go rename to examples/tokenvm/registry/registry.go index 38c17e4fdf..2be410f3b1 100644 --- a/examples/tokenvm/controller/registry.go +++ b/examples/tokenvm/registry/registry.go @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package controller +package registry import ( "github.com/ava-labs/avalanchego/utils/wrappers" diff --git a/listeners/errors.go b/examples/tokenvm/rpc/consts.go similarity index 59% rename from listeners/errors.go rename to examples/tokenvm/rpc/consts.go index abd1b5b348..58f7c95754 100644 --- a/listeners/errors.go +++ b/examples/tokenvm/rpc/consts.go @@ -1,8 +1,10 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package listeners +package rpc -import "errors" +const ( + JSONRPCEndpoint = "/tokenapi" -var ErrExpired = errors.New("expired") + ordersToSend = 128 +) diff --git a/examples/tokenvm/rpc/dependencies.go b/examples/tokenvm/rpc/dependencies.go new file mode 100644 index 0000000000..1c2f8996ae --- /dev/null +++ b/examples/tokenvm/rpc/dependencies.go @@ -0,0 +1,24 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rpc + +import ( + "context" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/trace" + "github.com/ava-labs/hypersdk/crypto" + "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" + "github.com/ava-labs/hypersdk/examples/tokenvm/orderbook" +) + +type Controller interface { + Genesis() *genesis.Genesis + Tracer() trace.Tracer + GetTransaction(context.Context, ids.ID) (bool, int64, bool, uint64, error) + GetAssetFromState(context.Context, ids.ID) (bool, []byte, uint64, crypto.PublicKey, bool, error) + GetBalanceFromState(context.Context, crypto.PublicKey, ids.ID) (uint64, error) + Orders(pair string, limit int) []*orderbook.Order + GetLoanFromState(context.Context, ids.ID, ids.ID) (uint64, error) +} diff --git a/examples/tokenvm/rpc/errors.go b/examples/tokenvm/rpc/errors.go new file mode 100644 index 0000000000..3f9175705e --- /dev/null +++ b/examples/tokenvm/rpc/errors.go @@ -0,0 +1,11 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rpc + +import "errors" + +var ( + ErrTxNotFound = errors.New("tx not found") + ErrAssetNotFound = errors.New("asset not found") +) diff --git a/examples/tokenvm/rpc/jsonrpc_client.go b/examples/tokenvm/rpc/jsonrpc_client.go new file mode 100644 index 0000000000..33c28e3f5a --- /dev/null +++ b/examples/tokenvm/rpc/jsonrpc_client.go @@ -0,0 +1,207 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rpc + +import ( + "context" + "strings" + + "github.com/ava-labs/avalanchego/ids" + + "github.com/ava-labs/hypersdk/chain" + "github.com/ava-labs/hypersdk/examples/tokenvm/consts" + "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" + "github.com/ava-labs/hypersdk/examples/tokenvm/orderbook" + _ "github.com/ava-labs/hypersdk/examples/tokenvm/registry" // ensure registry populated + "github.com/ava-labs/hypersdk/requester" + "github.com/ava-labs/hypersdk/rpc" + "github.com/ava-labs/hypersdk/utils" +) + +type JSONRPCClient struct { + requester *requester.EndpointRequester + + chainID ids.ID + g *genesis.Genesis +} + +// New creates a new client object. +func NewJSONRPCClient(uri string, chainID ids.ID) *JSONRPCClient { + uri = strings.TrimSuffix(uri, "/") + uri += JSONRPCEndpoint + req := requester.New(uri, consts.Name) + return &JSONRPCClient{req, chainID, nil} +} + +func (cli *JSONRPCClient) Genesis(ctx context.Context) (*genesis.Genesis, error) { + if cli.g != nil { + return cli.g, nil + } + + resp := new(GenesisReply) + err := cli.requester.SendRequest( + ctx, + "genesis", + nil, + resp, + ) + if err != nil { + return nil, err + } + cli.g = resp.Genesis + return resp.Genesis, nil +} + +func (cli *JSONRPCClient) Tx(ctx context.Context, id ids.ID) (bool, bool, int64, error) { + resp := new(TxReply) + err := cli.requester.SendRequest( + ctx, + "tx", + &TxArgs{TxID: id}, + resp, + ) + switch { + // We use string parsing here because the JSON-RPC library we use may not + // allows us to perform errors.Is. + case err != nil && strings.Contains(err.Error(), ErrTxNotFound.Error()): + return false, false, -1, nil + case err != nil: + return false, false, -1, err + } + return true, resp.Success, resp.Timestamp, nil +} + +func (cli *JSONRPCClient) Asset( + ctx context.Context, + asset ids.ID, +) (bool, []byte, uint64, string, bool, error) { + resp := new(AssetReply) + err := cli.requester.SendRequest( + ctx, + "asset", + &AssetArgs{ + Asset: asset, + }, + resp, + ) + switch { + // We use string parsing here because the JSON-RPC library we use may not + // allows us to perform errors.Is. + case err != nil && strings.Contains(err.Error(), ErrAssetNotFound.Error()): + return false, nil, 0, "", false, nil + case err != nil: + return false, nil, 0, "", false, err + } + return true, resp.Metadata, resp.Supply, resp.Owner, resp.Warp, nil +} + +func (cli *JSONRPCClient) Balance(ctx context.Context, addr string, asset ids.ID) (uint64, error) { + resp := new(BalanceReply) + err := cli.requester.SendRequest( + ctx, + "balance", + &BalanceArgs{ + Address: addr, + Asset: asset, + }, + resp, + ) + return resp.Amount, err +} + +func (cli *JSONRPCClient) Orders(ctx context.Context, pair string) ([]*orderbook.Order, error) { + resp := new(OrdersReply) + err := cli.requester.SendRequest( + ctx, + "orders", + &OrdersArgs{ + Pair: pair, + }, + resp, + ) + return resp.Orders, err +} + +func (cli *JSONRPCClient) Loan( + ctx context.Context, + asset ids.ID, + destination ids.ID, +) (uint64, error) { + resp := new(LoanReply) + err := cli.requester.SendRequest( + ctx, + "loan", + &LoanArgs{ + Asset: asset, + Destination: destination, + }, + resp, + ) + return resp.Amount, err +} + +func (cli *JSONRPCClient) WaitForBalance( + ctx context.Context, + addr string, + asset ids.ID, + min uint64, +) error { + return rpc.Wait(ctx, func(ctx context.Context) (bool, error) { + balance, err := cli.Balance(ctx, addr, asset) + if err != nil { + return false, err + } + shouldExit := balance >= min + if !shouldExit { + utils.Outf( + "{{yellow}}waiting for %s balance: %s{{/}}\n", + utils.FormatBalance(min), + addr, + ) + } + return shouldExit, nil + }) +} + +func (cli *JSONRPCClient) WaitForTransaction(ctx context.Context, txID ids.ID) (bool, error) { + var success bool + if err := rpc.Wait(ctx, func(ctx context.Context) (bool, error) { + found, isuccess, _, err := cli.Tx(ctx, txID) + if err != nil { + return false, err + } + success = isuccess + return found, nil + }); err != nil { + return false, err + } + return success, nil +} + +var _ chain.Parser = (*Parser)(nil) + +type Parser struct { + chainID ids.ID + genesis *genesis.Genesis +} + +func (p *Parser) ChainID() ids.ID { + return p.chainID +} + +func (p *Parser) Rules(t int64) chain.Rules { + return p.genesis.Rules(t) +} + +func (*Parser) Registry() (chain.ActionRegistry, chain.AuthRegistry) { + return consts.ActionRegistry, consts.AuthRegistry +} + +func (cli *JSONRPCClient) Parser(ctx context.Context) (chain.Parser, error) { + g, err := cli.Genesis(ctx) + if err != nil { + return nil, err + } + return &Parser{cli.chainID, g}, nil +} diff --git a/examples/tokenvm/controller/handler.go b/examples/tokenvm/rpc/jsonrpc_server.go similarity index 51% rename from examples/tokenvm/controller/handler.go rename to examples/tokenvm/rpc/jsonrpc_server.go index 4b575ec170..ecb9a3a065 100644 --- a/examples/tokenvm/controller/handler.go +++ b/examples/tokenvm/rpc/jsonrpc_server.go @@ -1,41 +1,32 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package controller +package rpc import ( - "errors" "net/http" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/hypersdk/vm" "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" - "github.com/ava-labs/hypersdk/examples/tokenvm/storage" + "github.com/ava-labs/hypersdk/examples/tokenvm/orderbook" "github.com/ava-labs/hypersdk/examples/tokenvm/utils" ) -const ( - ordersToSend = 128 -) - -var ( - ErrTxNotFound = errors.New("tx not found") - ErrAssetNotFound = errors.New("asset not found") -) - -type Handler struct { - *vm.Handler // embed standard functionality +type JSONRPCServer struct { + c Controller +} - c *Controller +func NewJSONRPCServer(c Controller) *JSONRPCServer { + return &JSONRPCServer{c} } type GenesisReply struct { Genesis *genesis.Genesis `json:"genesis"` } -func (h *Handler) Genesis(_ *http.Request, _ *struct{}, reply *GenesisReply) (err error) { - reply.Genesis = h.c.genesis +func (j *JSONRPCServer) Genesis(_ *http.Request, _ *struct{}, reply *GenesisReply) (err error) { + reply.Genesis = j.c.Genesis() return nil } @@ -49,11 +40,11 @@ type TxReply struct { Units uint64 `json:"units"` } -func (h *Handler) Tx(req *http.Request, args *TxArgs, reply *TxReply) error { - ctx, span := h.c.inner.Tracer().Start(req.Context(), "Handler.Tx") +func (j *JSONRPCServer) Tx(req *http.Request, args *TxArgs, reply *TxReply) error { + ctx, span := j.c.Tracer().Start(req.Context(), "Server.Tx") defer span.End() - found, t, success, units, err := storage.GetTransaction(ctx, h.c.metaDB, args.TxID) + found, t, success, units, err := j.c.GetTransaction(ctx, args.TxID) if err != nil { return err } @@ -77,15 +68,11 @@ type AssetReply struct { Warp bool `json:"warp"` } -func (h *Handler) Asset(req *http.Request, args *AssetArgs, reply *AssetReply) error { - ctx, span := h.c.inner.Tracer().Start(req.Context(), "Handler.Asset") +func (j *JSONRPCServer) Asset(req *http.Request, args *AssetArgs, reply *AssetReply) error { + ctx, span := j.c.Tracer().Start(req.Context(), "Server.Asset") defer span.End() - exists, metadata, supply, owner, warp, err := storage.GetAssetFromState( - ctx, - h.c.inner.ReadState, - args.Asset, - ) + exists, metadata, supply, owner, warp, err := j.c.GetAssetFromState(ctx, args.Asset) if err != nil { return err } @@ -108,15 +95,15 @@ type BalanceReply struct { Amount uint64 `json:"amount"` } -func (h *Handler) Balance(req *http.Request, args *BalanceArgs, reply *BalanceReply) error { - ctx, span := h.c.inner.Tracer().Start(req.Context(), "Handler.Balance") +func (j *JSONRPCServer) Balance(req *http.Request, args *BalanceArgs, reply *BalanceReply) error { + ctx, span := j.c.Tracer().Start(req.Context(), "Server.Balance") defer span.End() addr, err := utils.ParseAddress(args.Address) if err != nil { return err } - balance, err := storage.GetBalanceFromState(ctx, h.c.inner.ReadState, addr, args.Asset) + balance, err := j.c.GetBalanceFromState(ctx, addr, args.Asset) if err != nil { return err } @@ -129,14 +116,14 @@ type OrdersArgs struct { } type OrdersReply struct { - Orders []*Order `json:"orders"` + Orders []*orderbook.Order `json:"orders"` } -func (h *Handler) Orders(req *http.Request, args *OrdersArgs, reply *OrdersReply) error { - _, span := h.c.inner.Tracer().Start(req.Context(), "Handler.Orders") +func (j *JSONRPCServer) Orders(req *http.Request, args *OrdersArgs, reply *OrdersReply) error { + _, span := j.c.Tracer().Start(req.Context(), "Server.Orders") defer span.End() - reply.Orders = h.c.orderBook.Orders(args.Pair, ordersToSend) + reply.Orders = j.c.Orders(args.Pair, ordersToSend) return nil } @@ -149,11 +136,11 @@ type LoanReply struct { Amount uint64 `json:"amount"` } -func (h *Handler) Loan(req *http.Request, args *LoanArgs, reply *LoanReply) error { - ctx, span := h.c.inner.Tracer().Start(req.Context(), "Handler.Loan") +func (j *JSONRPCServer) Loan(req *http.Request, args *LoanArgs, reply *LoanReply) error { + ctx, span := j.c.Tracer().Start(req.Context(), "Server.Loan") defer span.End() - amount, err := storage.GetLoanFromState(ctx, h.c.inner.ReadState, args.Asset, args.Destination) + amount, err := j.c.GetLoanFromState(ctx, args.Asset, args.Destination) if err != nil { return err } diff --git a/examples/tokenvm/scripts/run.sh b/examples/tokenvm/scripts/run.sh index d3d3bf2682..0a5f3591a0 100755 --- a/examples/tokenvm/scripts/run.sh +++ b/examples/tokenvm/scripts/run.sh @@ -17,7 +17,7 @@ if ! [[ "$0" =~ scripts/run.sh ]]; then exit 255 fi -VERSION=1.9.16 +VERSION=1.10.0 MODE=${MODE:-run} LOGLEVEL=${LOGLEVEL:-info} STATESYNC_DELAY=${STATESYNC_DELAY:-0} @@ -165,7 +165,7 @@ ACK_GINKGO_RC=true ginkgo build ./tests/e2e # download avalanche-network-runner # https://github.com/ava-labs/avalanche-network-runner ANR_REPO_PATH=github.com/ava-labs/avalanche-network-runner -ANR_VERSION=fc888ba0646f4396456ba2b36eb56c26aa76a26a +ANR_VERSION=v1.4.1 # version set go install -v ${ANR_REPO_PATH}@${ANR_VERSION} diff --git a/examples/tokenvm/tests/e2e/e2e_test.go b/examples/tokenvm/tests/e2e/e2e_test.go index 1ead6c7c06..4c96b8a927 100644 --- a/examples/tokenvm/tests/e2e/e2e_test.go +++ b/examples/tokenvm/tests/e2e/e2e_test.go @@ -12,16 +12,18 @@ import ( runner_sdk "github.com/ava-labs/avalanche-network-runner/client" "github.com/ava-labs/avalanche-network-runner/rpcpb" + "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/hypersdk/crypto" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" - "github.com/ava-labs/hypersdk/examples/tokenvm/client" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/examples/tokenvm/utils" + "github.com/ava-labs/hypersdk/rpc" hutils "github.com/ava-labs/hypersdk/utils" "github.com/fatih/color" ginkgo "github.com/onsi/ginkgo/v2" @@ -61,6 +63,8 @@ var ( blockchainIDA string blockchainIDB string + + trackSubnetsOpt runner_sdk.OpOption ) func init() { @@ -216,59 +220,55 @@ var _ = ginkgo.BeforeSuite(func() { // Create 2 subnets ctx, cancel = context.WithTimeout(context.Background(), 5*time.Minute) - sresp, err := anrCli.CreateSpecificBlockchains( + sresp, err := anrCli.CreateBlockchains( ctx, - execPath, []*rpcpb.BlockchainSpec{ { - VmName: consts.Name, - Genesis: vmGenesisPath, - ChainConfig: vmConfigPath, - SubnetConfig: subnetConfigPath, - Participants: subnetA, + VmName: consts.Name, + Genesis: vmGenesisPath, + ChainConfig: vmConfigPath, + SubnetSpec: &rpcpb.SubnetSpec{ + SubnetConfig: subnetConfigPath, + Participants: subnetA, + }, }, { - VmName: consts.Name, - Genesis: vmGenesisPath, - ChainConfig: vmConfigPath, - SubnetConfig: subnetConfigPath, - Participants: subnetB, + VmName: consts.Name, + Genesis: vmGenesisPath, + ChainConfig: vmConfigPath, + SubnetSpec: &rpcpb.SubnetSpec{ + SubnetConfig: subnetConfigPath, + Participants: subnetB, + }, }, }, ) cancel() gomega.Expect(err).Should(gomega.BeNil()) - blockchainIDA = sresp.Chains[0] + blockchainIDA = sresp.ChainIds[0] + subnetIDA := sresp.ClusterInfo.CustomChains[blockchainIDA].SubnetId hutils.Outf( "{{green}}successfully added chain:{{/}} %s {{green}}subnet:{{/}} %s {{green}}participants:{{/}} %+v\n", blockchainIDA, - sresp.ClusterInfo.CustomChains[blockchainIDA].SubnetId, + subnetIDA, subnetA, ) - blockchainIDB = sresp.Chains[1] + + blockchainIDB = sresp.ChainIds[1] + subnetIDB := sresp.ClusterInfo.CustomChains[blockchainIDB].SubnetId hutils.Outf( "{{green}}successfully added chain:{{/}} %s {{green}}subnet:{{/}} %s {{green}}participants:{{/}} %+v\n", blockchainIDB, - sresp.ClusterInfo.CustomChains[blockchainIDB].SubnetId, + subnetIDB, subnetB, ) - // TODO: network runner health should imply custom VM healthiness - // or provide a separate API for custom VM healthiness - // "start" is async, so wait some time for cluster health - hutils.Outf("\n{{magenta}}waiting for all vms to report healthy...{{/}}: %s\n", consts.ID) - for { - v, err := anrCli.Health(context.Background()) - hutils.Outf("\n{{yellow}}health result{{/}}: %+v %s\n", v, err) - if err != nil { - time.Sleep(1 * time.Second) - continue - } - // TODO: clean this up - gomega.Expect(err).Should(gomega.BeNil()) - break - } + trackSubnetsOpt = runner_sdk.WithGlobalNodeConfig(fmt.Sprintf("{\"%s\":\"%s,%s\"}", + config.TrackSubnetsKey, + subnetIDA, + subnetIDB, + )) gomega.Expect(blockchainIDA).Should(gomega.Not(gomega.BeEmpty())) gomega.Expect(blockchainIDB).Should(gomega.Not(gomega.BeEmpty())) @@ -284,24 +284,30 @@ var _ = ginkgo.BeforeSuite(func() { for _, nodeName := range subnetA { info := nodeInfos[nodeName] u := fmt.Sprintf("%s/ext/bc/%s", info.Uri, blockchainIDA) + bid, err := ids.FromString(blockchainIDA) + gomega.Expect(err).Should(gomega.BeNil()) nodeID, err := ids.NodeIDFromString(info.GetId()) gomega.Expect(err).Should(gomega.BeNil()) instancesA = append(instancesA, instance{ nodeID: nodeID, uri: u, - cli: client.New(u), + cli: rpc.NewJSONRPCClient(u), + tcli: trpc.NewJSONRPCClient(u, bid), }) } instancesB = []instance{} for _, nodeName := range subnetB { info := nodeInfos[nodeName] u := fmt.Sprintf("%s/ext/bc/%s", info.Uri, blockchainIDB) + bid, err := ids.FromString(blockchainIDB) + gomega.Expect(err).Should(gomega.BeNil()) nodeID, err := ids.NodeIDFromString(info.GetId()) gomega.Expect(err).Should(gomega.BeNil()) instancesB = append(instancesB, instance{ nodeID: nodeID, uri: u, - cli: client.New(u), + cli: rpc.NewJSONRPCClient(u), + tcli: trpc.NewJSONRPCClient(u, bid), }) } @@ -314,7 +320,7 @@ var _ = ginkgo.BeforeSuite(func() { sender = utils.Address(rsender) hutils.Outf("\n{{yellow}}$ loaded address:{{/}} %s\n\n", sender) - gen, err = instancesA[0].cli.Genesis(context.Background()) + gen, err = instancesA[0].tcli.Genesis(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) }) @@ -333,7 +339,8 @@ var ( type instance struct { nodeID ids.NodeID uri string - cli *client.Client + cli *rpc.JSONRPCClient + tcli *trpc.JSONRPCClient } var _ = ginkgo.AfterSuite(func() { @@ -418,7 +425,7 @@ var _ = ginkgo.Describe("[Test]", func() { }) ginkgo.It("transfer in a single node (raw)", func() { - nativeBalance, err := instancesA[0].cli.Balance(context.TODO(), sender, ids.Empty) + nativeBalance, err := instancesA[0].tcli.Balance(context.TODO(), sender, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(nativeBalance).Should(gomega.Equal(uint64(1000000000000))) @@ -428,8 +435,11 @@ var _ = ginkgo.Describe("[Test]", func() { ginkgo.By("issue Transfer to the first node", func() { // Generate transaction + parser, err := instancesA[0].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, fee, err := instancesA[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: other.PublicKey(), @@ -444,14 +454,14 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) hutils.Outf("{{yellow}}submitted transaction{{/}}\n") ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, err := instancesA[0].cli.WaitForTransaction(ctx, tx.ID()) + success, err := instancesA[0].tcli.WaitForTransaction(ctx, tx.ID()) cancel() gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(success).Should(gomega.BeTrue()) hutils.Outf("{{yellow}}found transaction{{/}}\n") // Check sender balance - balance, err := instancesA[0].cli.Balance(context.Background(), sender, ids.Empty) + balance, err := instancesA[0].tcli.Balance(context.Background(), sender, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) hutils.Outf( "{{yellow}}start=%d fee=%d send=%d balance=%d{{/}}\n", @@ -479,7 +489,7 @@ var _ = ginkgo.Describe("[Test]", func() { } // Check balance of recipient - balance, err := inst.cli.Balance(context.Background(), aother, ids.Empty) + balance, err := inst.tcli.Balance(context.Background(), aother, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(sendAmount)) } @@ -498,13 +508,16 @@ var _ = ginkgo.Describe("[Test]", func() { var txID ids.ID ginkgo.By("submitting an export action on source", func() { - otherBalance, err := instancesA[0].cli.Balance(context.Background(), aother, ids.Empty) + otherBalance, err := instancesA[0].tcli.Balance(context.Background(), aother, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) - senderBalance, err := instancesA[0].cli.Balance(context.Background(), sender, ids.Empty) + senderBalance, err := instancesA[0].tcli.Balance(context.Background(), sender, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) + parser, err := instancesA[0].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, fees, err := instancesA[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.ExportAsset{ To: other.PublicKey(), @@ -523,20 +536,20 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) hutils.Outf("{{yellow}}submitted transaction{{/}}\n") ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, err := instancesA[0].cli.WaitForTransaction(ctx, tx.ID()) + success, err := instancesA[0].tcli.WaitForTransaction(ctx, tx.ID()) cancel() gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(success).Should(gomega.BeTrue()) hutils.Outf("{{yellow}}found warp export transaction{{/}}\n") // Check loans and balances - amount, err := instancesA[0].cli.Loan(context.Background(), ids.Empty, destination) + amount, err := instancesA[0].tcli.Loan(context.Background(), ids.Empty, destination) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(amount).Should(gomega.Equal(sendAmount)) - aotherBalance, err := instancesA[0].cli.Balance(context.Background(), aother, ids.Empty) + aotherBalance, err := instancesA[0].tcli.Balance(context.Background(), aother, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(otherBalance).Should(gomega.Equal(aotherBalance)) - asenderBalance, err := instancesA[0].cli.Balance( + asenderBalance, err := instancesA[0].tcli.Balance( context.Background(), sender, ids.Empty, @@ -550,8 +563,11 @@ var _ = ginkgo.Describe("[Test]", func() { func() { txs := make([]ids.ID, 5) for i := 0; i < 5; i++ { + parser, err := instancesB[0].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := instancesB[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: other.PublicKey(), @@ -575,7 +591,7 @@ var _ = ginkgo.Describe("[Test]", func() { for i := 0; i < 5; i++ { txID := txs[i] ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, err := instancesB[0].cli.WaitForTransaction(ctx, txID) + success, err := instancesB[0].tcli.WaitForTransaction(ctx, txID) cancel() gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(success).Should(gomega.BeTrue()) @@ -588,26 +604,26 @@ var _ = ginkgo.Describe("[Test]", func() { bIDA, err := ids.FromString(blockchainIDA) gomega.Ω(err).Should(gomega.BeNil()) newAsset := actions.ImportedAssetID(ids.Empty, bIDA) - nativeOtherBalance, err := instancesB[0].cli.Balance( + nativeOtherBalance, err := instancesB[0].tcli.Balance( context.Background(), aother, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) - newOtherBalance, err := instancesB[0].cli.Balance( + newOtherBalance, err := instancesB[0].tcli.Balance( context.Background(), aother, newAsset, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(newOtherBalance).Should(gomega.Equal(uint64(0))) - nativeSenderBalance, err := instancesB[0].cli.Balance( + nativeSenderBalance, err := instancesB[0].tcli.Balance( context.Background(), sender, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) - newSenderBalance, err := instancesB[0].cli.Balance( + newSenderBalance, err := instancesB[0].tcli.Balance( context.Background(), sender, newAsset, @@ -645,8 +661,11 @@ var _ = ginkgo.Describe("[Test]", func() { ) gomega.Ω(subnetWeight).Should(gomega.Equal(sigWeight)) + parser, err := instancesB[0].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, fees, err := instancesB[0].cli.GenerateTransaction( context.Background(), + parser, msg, &actions.ImportAsset{}, factory, @@ -657,42 +676,42 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) hutils.Outf("{{yellow}}submitted transaction{{/}}\n") ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, err := instancesB[0].cli.WaitForTransaction(ctx, tx.ID()) + success, err := instancesB[0].tcli.WaitForTransaction(ctx, tx.ID()) cancel() gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(success).Should(gomega.BeTrue()) hutils.Outf("{{yellow}}found warp import transaction{{/}}\n") // Check asset info and balance - aNativeOtherBalance, err := instancesB[0].cli.Balance( + aNativeOtherBalance, err := instancesB[0].tcli.Balance( context.Background(), aother, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(nativeOtherBalance).Should(gomega.Equal(aNativeOtherBalance)) - aNewOtherBalance, err := instancesB[0].cli.Balance( + aNewOtherBalance, err := instancesB[0].tcli.Balance( context.Background(), aother, newAsset, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(aNewOtherBalance).Should(gomega.Equal(sendAmount)) - aNativeSenderBalance, err := instancesB[0].cli.Balance( + aNativeSenderBalance, err := instancesB[0].tcli.Balance( context.Background(), sender, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(aNativeSenderBalance).Should(gomega.Equal(nativeSenderBalance - fees)) - aNewSenderBalance, err := instancesB[0].cli.Balance( + aNewSenderBalance, err := instancesB[0].tcli.Balance( context.Background(), sender, newAsset, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(aNewSenderBalance).Should(gomega.Equal(uint64(0))) - exists, metadata, supply, owner, warp, err := instancesB[0].cli.Asset( + exists, metadata, supply, owner, warp, err := instancesB[0].tcli.Asset( context.Background(), newAsset, ) @@ -708,8 +727,11 @@ var _ = ginkgo.Describe("[Test]", func() { bIDA, err := ids.FromString(blockchainIDA) gomega.Ω(err).Should(gomega.BeNil()) newAsset := actions.ImportedAssetID(ids.Empty, bIDA) + parser, err := instancesB[0].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := instancesB[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.ExportAsset{ To: rsender, @@ -727,13 +749,13 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) hutils.Outf("{{yellow}}submitted transaction{{/}}\n") ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, err := instancesB[0].cli.WaitForTransaction(ctx, tx.ID()) + success, err := instancesB[0].tcli.WaitForTransaction(ctx, tx.ID()) cancel() gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(success).Should(gomega.BeFalse()) // Confirm balances are unchanged - newOtherBalance, err := instancesB[0].cli.Balance( + newOtherBalance, err := instancesB[0].tcli.Balance( context.Background(), aother, newAsset, @@ -746,8 +768,11 @@ var _ = ginkgo.Describe("[Test]", func() { bIDA, err := ids.FromString(blockchainIDA) gomega.Ω(err).Should(gomega.BeNil()) newAsset := actions.ImportedAssetID(ids.Empty, bIDA) + parser, err := instancesB[0].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := instancesB[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.ExportAsset{ To: rsender, @@ -767,20 +792,20 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) hutils.Outf("{{yellow}}submitted transaction{{/}}\n") ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, err := instancesB[0].cli.WaitForTransaction(ctx, tx.ID()) + success, err := instancesB[0].tcli.WaitForTransaction(ctx, tx.ID()) cancel() gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(success).Should(gomega.BeTrue()) hutils.Outf("{{yellow}}found warp export transaction{{/}}\n") // Check balances and asset info - amount, err := instancesB[0].cli.Loan(context.Background(), newAsset, source) + amount, err := instancesB[0].tcli.Loan(context.Background(), newAsset, source) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(amount).Should(gomega.Equal(uint64(0))) - otherBalance, err := instancesB[0].cli.Balance(context.Background(), aother, newAsset) + otherBalance, err := instancesB[0].tcli.Balance(context.Background(), aother, newAsset) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(otherBalance).Should(gomega.Equal(uint64(2900))) - exists, metadata, supply, owner, warp, err := instancesB[0].cli.Asset( + exists, metadata, supply, owner, warp, err := instancesB[0].tcli.Asset( context.Background(), newAsset, ) @@ -796,26 +821,26 @@ var _ = ginkgo.Describe("[Test]", func() { bIDA, err := ids.FromString(blockchainIDA) gomega.Ω(err).Should(gomega.BeNil()) newAsset := actions.ImportedAssetID(ids.Empty, bIDA) - nativeOtherBalance, err := instancesA[0].cli.Balance( + nativeOtherBalance, err := instancesA[0].tcli.Balance( context.Background(), aother, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) - newOtherBalance, err := instancesA[0].cli.Balance( + newOtherBalance, err := instancesA[0].tcli.Balance( context.Background(), aother, newAsset, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(newOtherBalance).Should(gomega.Equal(uint64(0))) - nativeSenderBalance, err := instancesA[0].cli.Balance( + nativeSenderBalance, err := instancesA[0].tcli.Balance( context.Background(), sender, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) - newSenderBalance, err := instancesA[0].cli.Balance( + newSenderBalance, err := instancesA[0].tcli.Balance( context.Background(), sender, newAsset, @@ -853,8 +878,11 @@ var _ = ginkgo.Describe("[Test]", func() { ) gomega.Ω(subnetWeight).Should(gomega.Equal(sigWeight)) + parser, err := instancesA[0].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, fees, err := instancesA[0].cli.GenerateTransaction( context.Background(), + parser, msg, &actions.ImportAsset{}, factory, @@ -865,28 +893,28 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) hutils.Outf("{{yellow}}submitted transaction{{/}}\n") ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, err := instancesA[0].cli.WaitForTransaction(ctx, tx.ID()) + success, err := instancesA[0].tcli.WaitForTransaction(ctx, tx.ID()) cancel() gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(success).Should(gomega.BeTrue()) hutils.Outf("{{yellow}}found warp import transaction{{/}}\n") // Check balances and loan - aNativeOtherBalance, err := instancesA[0].cli.Balance( + aNativeOtherBalance, err := instancesA[0].tcli.Balance( context.Background(), aother, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(nativeOtherBalance).Should(gomega.Equal(aNativeOtherBalance)) - aNewOtherBalance, err := instancesA[0].cli.Balance( + aNewOtherBalance, err := instancesA[0].tcli.Balance( context.Background(), aother, newAsset, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(aNewOtherBalance).Should(gomega.Equal(uint64(0))) - aNativeSenderBalance, err := instancesA[0].cli.Balance( + aNativeSenderBalance, err := instancesA[0].tcli.Balance( context.Background(), sender, ids.Empty, @@ -894,14 +922,14 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(aNativeSenderBalance). Should(gomega.Equal(nativeSenderBalance - fees + 2000 + 100)) - aNewSenderBalance, err := instancesA[0].cli.Balance( + aNewSenderBalance, err := instancesA[0].tcli.Balance( context.Background(), sender, newAsset, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(aNewSenderBalance).Should(gomega.Equal(uint64(0))) - amount, err := instancesA[0].cli.Loan(context.Background(), ids.Empty, destination) + amount, err := instancesA[0].tcli.Loan(context.Background(), ids.Empty, destination) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(amount).Should(gomega.Equal(uint64(2900))) }) @@ -910,8 +938,11 @@ var _ = ginkgo.Describe("[Test]", func() { bIDA, err := ids.FromString(blockchainIDA) gomega.Ω(err).Should(gomega.BeNil()) newAsset := actions.ImportedAssetID(ids.Empty, bIDA) + parser, err := instancesB[0].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := instancesB[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.ExportAsset{ To: other.PublicKey(), @@ -930,20 +961,20 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) hutils.Outf("{{yellow}}submitted transaction{{/}}\n") ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, err := instancesB[0].cli.WaitForTransaction(ctx, tx.ID()) + success, err := instancesB[0].tcli.WaitForTransaction(ctx, tx.ID()) cancel() gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(success).Should(gomega.BeTrue()) hutils.Outf("{{yellow}}found warp export transaction{{/}}\n") // Check balances and asset info - amount, err := instancesB[0].cli.Loan(context.Background(), newAsset, source) + amount, err := instancesB[0].tcli.Loan(context.Background(), newAsset, source) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(amount).Should(gomega.Equal(uint64(0))) - otherBalance, err := instancesB[0].cli.Balance(context.Background(), aother, newAsset) + otherBalance, err := instancesB[0].tcli.Balance(context.Background(), aother, newAsset) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(otherBalance).Should(gomega.Equal(uint64(0))) - exists, _, _, _, _, err := instancesB[0].cli.Asset(context.Background(), newAsset) + exists, _, _, _, _, err := instancesB[0].tcli.Asset(context.Background(), newAsset) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(exists).Should(gomega.BeFalse()) }) @@ -952,26 +983,26 @@ var _ = ginkgo.Describe("[Test]", func() { bIDA, err := ids.FromString(blockchainIDA) gomega.Ω(err).Should(gomega.BeNil()) newAsset := actions.ImportedAssetID(ids.Empty, bIDA) - nativeOtherBalance, err := instancesA[0].cli.Balance( + nativeOtherBalance, err := instancesA[0].tcli.Balance( context.Background(), aother, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) - newOtherBalance, err := instancesA[0].cli.Balance( + newOtherBalance, err := instancesA[0].tcli.Balance( context.Background(), aother, newAsset, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(newOtherBalance).Should(gomega.Equal(uint64(0))) - nativeSenderBalance, err := instancesA[0].cli.Balance( + nativeSenderBalance, err := instancesA[0].tcli.Balance( context.Background(), sender, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) - newSenderBalance, err := instancesA[0].cli.Balance( + newSenderBalance, err := instancesA[0].tcli.Balance( context.Background(), sender, newAsset, @@ -1009,8 +1040,11 @@ var _ = ginkgo.Describe("[Test]", func() { ) gomega.Ω(subnetWeight).Should(gomega.Equal(sigWeight)) + parser, err := instancesA[0].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, fees, err := instancesA[0].cli.GenerateTransaction( context.Background(), + parser, msg, &actions.ImportAsset{}, factory, @@ -1021,42 +1055,42 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) hutils.Outf("{{yellow}}submitted transaction{{/}}\n") ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, err := instancesA[0].cli.WaitForTransaction(ctx, tx.ID()) + success, err := instancesA[0].tcli.WaitForTransaction(ctx, tx.ID()) cancel() gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(success).Should(gomega.BeTrue()) hutils.Outf("{{yellow}}found warp import transaction{{/}}\n") // Check balances and loan - aNativeOtherBalance, err := instancesA[0].cli.Balance( + aNativeOtherBalance, err := instancesA[0].tcli.Balance( context.Background(), aother, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(aNativeOtherBalance).Should(gomega.Equal(nativeOtherBalance + 2900)) - aNewOtherBalance, err := instancesA[0].cli.Balance( + aNewOtherBalance, err := instancesA[0].tcli.Balance( context.Background(), aother, newAsset, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(aNewOtherBalance).Should(gomega.Equal(uint64(0))) - aNativeSenderBalance, err := instancesA[0].cli.Balance( + aNativeSenderBalance, err := instancesA[0].tcli.Balance( context.Background(), sender, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(aNativeSenderBalance).Should(gomega.Equal(nativeSenderBalance - fees)) - aNewSenderBalance, err := instancesA[0].cli.Balance( + aNewSenderBalance, err := instancesA[0].tcli.Balance( context.Background(), sender, newAsset, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(aNewSenderBalance).Should(gomega.Equal(uint64(0))) - amount, err := instancesA[0].cli.Loan(context.Background(), ids.Empty, destination) + amount, err := instancesA[0].tcli.Loan(context.Background(), ids.Empty, destination) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(amount).Should(gomega.Equal(uint64(0))) }) @@ -1065,8 +1099,11 @@ var _ = ginkgo.Describe("[Test]", func() { bIDA, err := ids.FromString(blockchainIDA) gomega.Ω(err).Should(gomega.BeNil()) newAsset := actions.ImportedAssetID(ids.Empty, bIDA) + parser, err := instancesA[0].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := instancesA[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.ExportAsset{ To: other.PublicKey(), @@ -1089,33 +1126,33 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) hutils.Outf("{{yellow}}submitted transaction{{/}}\n") ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, err := instancesA[0].cli.WaitForTransaction(ctx, tx.ID()) + success, err := instancesA[0].tcli.WaitForTransaction(ctx, tx.ID()) cancel() gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(success).Should(gomega.BeTrue()) hutils.Outf("{{yellow}}found warp export transaction{{/}}\n") // Record balances on destination - nativeOtherBalance, err := instancesB[0].cli.Balance( + nativeOtherBalance, err := instancesB[0].tcli.Balance( context.Background(), aother, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) - newOtherBalance, err := instancesB[0].cli.Balance( + newOtherBalance, err := instancesB[0].tcli.Balance( context.Background(), aother, newAsset, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(newOtherBalance).Should(gomega.Equal(uint64(0))) - nativeSenderBalance, err := instancesB[0].cli.Balance( + nativeSenderBalance, err := instancesB[0].tcli.Balance( context.Background(), sender, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) - newSenderBalance, err := instancesB[0].cli.Balance( + newSenderBalance, err := instancesB[0].tcli.Balance( context.Background(), sender, newAsset, @@ -1153,8 +1190,11 @@ var _ = ginkgo.Describe("[Test]", func() { ) gomega.Ω(subnetWeight).Should(gomega.Equal(sigWeight)) + parser, err = instancesB[0].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, fees, err := instancesB[0].cli.GenerateTransaction( context.Background(), + parser, msg, &actions.ImportAsset{ Fill: true, @@ -1167,28 +1207,28 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) hutils.Outf("{{yellow}}submitted transaction{{/}}\n") ctx, cancel = context.WithTimeout(context.Background(), requestTimeout) - success, err = instancesB[0].cli.WaitForTransaction(ctx, tx.ID()) + success, err = instancesB[0].tcli.WaitForTransaction(ctx, tx.ID()) cancel() gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(success).Should(gomega.BeTrue()) hutils.Outf("{{yellow}}found warp import transaction{{/}}\n") // Check balances following swap - aNativeOtherBalance, err := instancesB[0].cli.Balance( + aNativeOtherBalance, err := instancesB[0].tcli.Balance( context.Background(), aother, ids.Empty, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(aNativeOtherBalance).Should(gomega.Equal(nativeOtherBalance + 200)) - aNewOtherBalance, err := instancesB[0].cli.Balance( + aNewOtherBalance, err := instancesB[0].tcli.Balance( context.Background(), aother, newAsset, ) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(aNewOtherBalance).Should(gomega.Equal(uint64(1900))) - aNativeSenderBalance, err := instancesB[0].cli.Balance( + aNativeSenderBalance, err := instancesB[0].tcli.Balance( context.Background(), sender, ids.Empty, @@ -1196,7 +1236,7 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(aNativeSenderBalance). Should(gomega.Equal(nativeSenderBalance - fees - 200)) - aNewSenderBalance, err := instancesB[0].cli.Balance( + aNewSenderBalance, err := instancesB[0].tcli.Balance( context.Background(), sender, newAsset, @@ -1224,8 +1264,11 @@ var _ = ginkgo.Describe("[Test]", func() { // Generate transaction other, err := crypto.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) + parser, err := instancesA[count%len(instancesA)].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instancesA[count%len(instancesA)].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: other.PublicKey(), @@ -1256,24 +1299,31 @@ var _ = ginkgo.Describe("[Test]", func() { }) // Ensure bootstrapping works - var syncClient *client.Client + var syncClient *rpc.JSONRPCClient + var tsyncClient *trpc.JSONRPCClient ginkgo.It("can bootstrap a new node", func() { cluster, err := anrCli.AddNode( context.Background(), "bootstrap", execPath, + trackSubnetsOpt, ) gomega.Expect(err).To(gomega.BeNil()) awaitHealthy(anrCli) nodeURI := cluster.ClusterInfo.NodeInfos["bootstrap"].Uri uri := nodeURI + fmt.Sprintf("/ext/bc/%s", blockchainIDA) + bid, err := ids.FromString(blockchainIDA) + gomega.Expect(err).To(gomega.BeNil()) hutils.Outf("{{blue}}bootstrap node uri: %s{{/}}\n", uri) - c := client.New(uri) + c := rpc.NewJSONRPCClient(uri) syncClient = c + tc := trpc.NewJSONRPCClient(uri, bid) + tsyncClient = tc instancesA = append(instancesA, instance{ - uri: uri, - cli: c, + uri: uri, + cli: c, + tcli: tc, }) }) @@ -1282,8 +1332,11 @@ var _ = ginkgo.Describe("[Test]", func() { // Generate transaction other, err := crypto.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) + parser, err := tsyncClient.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := syncClient.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: other.PublicKey(), @@ -1298,7 +1351,7 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) hutils.Outf("{{yellow}}submitted transaction{{/}}\n") ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, err := syncClient.WaitForTransaction(ctx, tx.ID()) + success, err := tsyncClient.WaitForTransaction(ctx, tx.ID()) cancel() if err != nil { hutils.Outf("{{red}}cannot find transaction: %v{{/}}\n", err) @@ -1319,8 +1372,11 @@ var _ = ginkgo.Describe("[Test]", func() { // Generate transaction other, err := crypto.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) + parser, err := instancesA[count%len(instancesA)].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instancesA[count%len(instancesA)].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: other.PublicKey(), @@ -1357,6 +1413,7 @@ var _ = ginkgo.Describe("[Test]", func() { context.Background(), "sync", execPath, + trackSubnetsOpt, ) gomega.Expect(err).To(gomega.BeNil()) @@ -1369,8 +1426,11 @@ var _ = ginkgo.Describe("[Test]", func() { nodeURI := cluster.ClusterInfo.NodeInfos["sync"].Uri uri := nodeURI + fmt.Sprintf("/ext/bc/%s", blockchainIDA) + bid, err := ids.FromString(blockchainIDA) + gomega.Expect(err).To(gomega.BeNil()) hutils.Outf("{{blue}}sync node uri: %s{{/}}\n", uri) - syncClient = client.New(uri) + syncClient = rpc.NewJSONRPCClient(uri) + tsyncClient = trpc.NewJSONRPCClient(uri, bid) }) ginkgo.It("accepts transaction after state sync", func() { @@ -1378,8 +1438,11 @@ var _ = ginkgo.Describe("[Test]", func() { // Generate transaction other, err := crypto.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) + parser, err := tsyncClient.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := syncClient.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: other.PublicKey(), @@ -1394,7 +1457,7 @@ var _ = ginkgo.Describe("[Test]", func() { gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) hutils.Outf("{{yellow}}submitted transaction{{/}}\n") ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, err := syncClient.WaitForTransaction(ctx, tx.ID()) + success, err := tsyncClient.WaitForTransaction(ctx, tx.ID()) cancel() if err != nil { hutils.Outf("{{red}}cannot find transaction: %v{{/}}\n", err) @@ -1418,8 +1481,11 @@ var _ = ginkgo.Describe("[Test]", func() { for ctx.Err() == nil { other, err := crypto.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) + parser, err := instancesA[count%len(instancesA)].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instancesA[count%len(instancesA)].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: other.PublicKey(), @@ -1472,14 +1538,18 @@ var _ = ginkgo.Describe("[Test]", func() { context.Background(), "sync_concurrent", execPath, + trackSubnetsOpt, ) gomega.Expect(err).To(gomega.BeNil()) awaitHealthy(anrCli) nodeURI := cluster.ClusterInfo.NodeInfos["sync_concurrent"].Uri uri := nodeURI + fmt.Sprintf("/ext/bc/%s", blockchainIDA) + bid, err := ids.FromString(blockchainIDA) + gomega.Expect(err).To(gomega.BeNil()) hutils.Outf("{{blue}}sync node uri: %s{{/}}\n", uri) - syncClient = client.New(uri) + syncClient = rpc.NewJSONRPCClient(uri) + tsyncClient = trpc.NewJSONRPCClient(uri, bid) cancel() }) @@ -1488,8 +1558,11 @@ var _ = ginkgo.Describe("[Test]", func() { // Generate transaction other, err := crypto.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) + parser, err := tsyncClient.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := syncClient.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: other.PublicKey(), @@ -1502,7 +1575,7 @@ var _ = ginkgo.Describe("[Test]", func() { // Broadcast and wait for transaction gomega.Ω(submit(context.Background())).Should(gomega.BeNil()) ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - success, err := syncClient.WaitForTransaction(ctx, tx.ID()) + success, err := tsyncClient.WaitForTransaction(ctx, tx.ID()) cancel() if err != nil { hutils.Outf("{{red}}cannot find transaction: %v{{/}}\n", err) @@ -1536,8 +1609,11 @@ func awaitHealthy(cli runner_sdk.Client) { // [ValidityWindow] processed) other, err := crypto.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) + parser, err := instancesA[0].tcli.Parser(context.TODO()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instancesA[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: other.PublicKey(), diff --git a/examples/tokenvm/tests/integration/integration_test.go b/examples/tokenvm/tests/integration/integration_test.go index 6844f6ed47..1fd91233df 100644 --- a/examples/tokenvm/tests/integration/integration_test.go +++ b/examples/tokenvm/tests/integration/integration_test.go @@ -37,15 +37,16 @@ import ( "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/crypto" + "github.com/ava-labs/hypersdk/rpc" hutils "github.com/ava-labs/hypersdk/utils" "github.com/ava-labs/hypersdk/vm" "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" - "github.com/ava-labs/hypersdk/examples/tokenvm/client" tconsts "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/controller" "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/examples/tokenvm/utils" ) @@ -125,12 +126,15 @@ var ( ) type instance struct { - chainID ids.ID - nodeID ids.NodeID - vm *vm.VM - toEngine chan common.Message - httpServer *httptest.Server - cli *client.Client // clients for embedded VMs + chainID ids.ID + nodeID ids.NodeID + vm *vm.VM + toEngine chan common.Message + JSONRPCServer *httptest.Server + TokenJSONRPCServer *httptest.Server + WebSocketServer *httptest.Server + cli *rpc.JSONRPCClient // clients for embedded VMs + tcli *trpc.JSONRPCClient } var _ = ginkgo.BeforeSuite(func() { @@ -229,14 +233,19 @@ var _ = ginkgo.BeforeSuite(func() { hd, err = v.CreateHandlers(context.TODO()) gomega.Ω(err).Should(gomega.BeNil()) - httpServer := httptest.NewServer(hd[vm.Endpoint].Handler) + jsonRPCServer := httptest.NewServer(hd[rpc.JSONRPCEndpoint].Handler) + tjsonRPCServer := httptest.NewServer(hd[trpc.JSONRPCEndpoint].Handler) + webSocketServer := httptest.NewServer(hd[rpc.WebSocketEndpoint].Handler) instances[i] = instance{ - chainID: snowCtx.ChainID, - nodeID: snowCtx.NodeID, - vm: v, - toEngine: toEngine, - httpServer: httpServer, - cli: client.New(httpServer.URL), + chainID: snowCtx.ChainID, + nodeID: snowCtx.NodeID, + vm: v, + toEngine: toEngine, + JSONRPCServer: jsonRPCServer, + TokenJSONRPCServer: tjsonRPCServer, + WebSocketServer: webSocketServer, + cli: rpc.NewJSONRPCClient(jsonRPCServer.URL), + tcli: trpc.NewJSONRPCClient(tjsonRPCServer.URL, snowCtx.ChainID), } // Force sync ready (to mimic bootstrapping from genesis) @@ -246,7 +255,7 @@ var _ = ginkgo.BeforeSuite(func() { // Verify genesis allocations loaded correctly (do here otherwise test may // check during and it will be inaccurate) for _, inst := range instances { - cli := inst.cli + cli := inst.tcli g, err := cli.Genesis(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) @@ -272,7 +281,9 @@ var _ = ginkgo.BeforeSuite(func() { var _ = ginkgo.AfterSuite(func() { for _, iv := range instances { - iv.httpServer.Close() + iv.JSONRPCServer.Close() + iv.TokenJSONRPCServer.Close() + iv.WebSocketServer.Close() err := iv.vm.Shutdown(context.TODO()) gomega.Ω(err).Should(gomega.BeNil()) } @@ -314,8 +325,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { var transferTxRoot *chain.Transaction ginkgo.It("Gossip TransferTx to a different node", func() { ginkgo.By("issue TransferTx", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, transferTx, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: rsender2, @@ -412,10 +426,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.By("ensure balance is updated", func() { - balance, err := instances[1].cli.Balance(context.Background(), sender, ids.Empty) + balance, err := instances[1].tcli.Balance(context.Background(), sender, ids.Empty) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance).To(gomega.Equal(uint64(9899528))) - balance2, err := instances[1].cli.Balance(context.Background(), sender2, ids.Empty) + balance2, err := instances[1].tcli.Balance(context.Background(), sender2, ids.Empty) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance2).To(gomega.Equal(uint64(100000))) }) @@ -423,8 +437,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { ginkgo.It("ensure multiple txs work ", func() { ginkgo.By("transfer funds again", func() { + parser, err := instances[1].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[1].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: rsender2, @@ -439,7 +456,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance2, err := instances[1].cli.Balance(context.Background(), sender2, ids.Empty) + balance2, err := instances[1].tcli.Balance(context.Background(), sender2, ids.Empty) gomega.Ω(err).To(gomega.BeNil()) gomega.Ω(balance2).To(gomega.Equal(uint64(100101))) }) @@ -449,8 +466,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { var accept, accept2 func() []*chain.Result ginkgo.By("create processing tip", func() { + parser, err := instances[1].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[1].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: rsender2, @@ -464,6 +484,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err = instances[1].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: rsender2, @@ -488,8 +509,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { ginkgo.It("ensure mempool works", func() { ginkgo.By("fail Gossip TransferTx to a stale node when missing previous blocks", func() { + parser, err := instances[1].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[1].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.Transfer{ To: rsender2, @@ -567,15 +591,12 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { accept() // don't care about results // Subscribe to blocks - streamingPort, err := instances[0].cli.StreamingPort(context.TODO()) - gomega.Ω(err).Should(gomega.BeNil()) - gomega.Ω(streamingPort).Should(gomega.Not(gomega.Equal(0))) - tcpURI := fmt.Sprintf("127.0.0.1:%d", 4000) - cli, err := vm.NewStreamingClient(tcpURI) + cli, err := rpc.NewWebSocketClient(instances[0].WebSocketServer.URL) gomega.Ω(err).Should(gomega.BeNil()) + gomega.Ω(cli.RegisterBlocks()).Should(gomega.BeNil()) // Fetch balances - balance, err := instances[0].cli.Balance(context.TODO(), sender, ids.Empty) + balance, err := instances[0].tcli.Balance(context.TODO(), sender, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) // Send tx @@ -586,8 +607,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { Value: 1, } + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, rawTx, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, transfer, factory, @@ -602,33 +626,31 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results[0].Success).Should(gomega.BeTrue()) // Read item from connection - blk, lresults, err := cli.ListenForBlock(instances[0].vm) + blk, lresults, err := cli.ListenBlock(context.TODO(), parser) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(len(blk.Txs)).Should(gomega.Equal(1)) tx := blk.Txs[0].Action.(*actions.Transfer) gomega.Ω(tx.Asset).To(gomega.Equal(ids.Empty)) gomega.Ω(tx.Value).To(gomega.Equal(uint64(1))) gomega.Ω(lresults).Should(gomega.Equal(results)) - gomega.Ω(cli.Close()).Should(gomega.BeNil()) // Check balance modifications are correct - balancea, err := instances[0].cli.Balance(context.TODO(), sender, ids.Empty) + balancea, err := instances[0].tcli.Balance(context.TODO(), sender, ids.Empty) gomega.Ω(err).Should(gomega.BeNil()) - g, err := instances[0].cli.Genesis(context.TODO()) + g, err := instances[0].tcli.Genesis(context.TODO()) gomega.Ω(err).Should(gomega.BeNil()) r := g.Rules(time.Now().Unix()) maxUnits, err := rawTx.MaxUnits(r) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(balancea + maxUnits + 1)) + + // Close connection when done + gomega.Ω(cli.Close()).Should(gomega.BeNil()) }) ginkgo.It("processes valid index transactions (w/streaming verification)", func() { // Create streaming client - streamingPort, err := instances[0].cli.StreamingPort(context.TODO()) - gomega.Ω(err).Should(gomega.BeNil()) - gomega.Ω(streamingPort).Should(gomega.Not(gomega.Equal(0))) - tcpURI := fmt.Sprintf("127.0.0.1:%d", streamingPort) - cli, err := vm.NewStreamingClient(tcpURI) + cli, err := rpc.NewWebSocketClient(instances[0].WebSocketServer.URL) gomega.Ω(err).Should(gomega.BeNil()) // Create tx @@ -638,8 +660,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { To: other.PublicKey(), Value: 1, } + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) _, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, transfer, factory, @@ -647,7 +672,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) // Submit tx and accept block - gomega.Ω(cli.IssueTx(tx)).Should(gomega.BeNil()) + gomega.Ω(cli.RegisterTx(tx)).Should(gomega.BeNil()) for instances[0].vm.Mempool().Len(context.TODO()) == 0 { // We need to wait for mempool to be populated because issuance will // return as soon as bytes are on the channel. @@ -661,12 +686,14 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results[0].Success).Should(gomega.BeTrue()) // Read decision from connection - txID, dErr, result, err := cli.ListenForTx() + txID, dErr, result, err := cli.ListenTx(context.TODO()) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(txID).Should(gomega.Equal(tx.ID())) gomega.Ω(dErr).Should(gomega.BeNil()) gomega.Ω(result.Success).Should(gomega.BeTrue()) gomega.Ω(result).Should(gomega.Equal(results[0])) + + // Close connection when done gomega.Ω(cli.Close()).Should(gomega.BeNil()) }) @@ -674,8 +701,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { other, err := crypto.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) assetID := ids.GenerateTestID() + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.MintAsset{ To: other.PublicKey(), @@ -694,14 +724,17 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(string(result.Output)). Should(gomega.ContainSubstring("asset missing")) - exists, _, _, _, _, err := instances[0].cli.Asset(context.TODO(), assetID) + exists, _, _, _, _, err := instances[0].tcli.Asset(context.TODO(), assetID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(exists).Should(gomega.BeFalse()) }) ginkgo.It("create a new asset (no metadata)", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.CreateAsset{ Metadata: nil, @@ -716,10 +749,10 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results[0].Success).Should(gomega.BeTrue()) assetID := tx.ID() - balance, err := instances[0].cli.Balance(context.TODO(), sender, assetID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender, assetID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(0))) - exists, metadata, supply, owner, warp, err := instances[0].cli.Asset( + exists, metadata, supply, owner, warp, err := instances[0].tcli.Asset( context.TODO(), assetID, ) @@ -762,8 +795,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("create a new asset (simple metadata)", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.CreateAsset{ Metadata: asset1, @@ -778,11 +814,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results[0].Success).Should(gomega.BeTrue()) asset1ID = tx.ID() - balance, err := instances[0].cli.Balance(context.TODO(), sender, asset1ID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender, asset1ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(0))) - exists, metadata, supply, owner, warp, err := instances[0].cli.Asset( + exists, metadata, supply, owner, warp, err := instances[0].tcli.Asset( context.TODO(), asset1ID, ) @@ -795,8 +831,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("mint a new asset", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.MintAsset{ To: rsender2, @@ -812,14 +851,14 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance, err := instances[0].cli.Balance(context.TODO(), sender2, asset1ID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender2, asset1ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(15))) - balance, err = instances[0].cli.Balance(context.TODO(), sender, asset1ID) + balance, err = instances[0].tcli.Balance(context.TODO(), sender, asset1ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(0))) - exists, metadata, supply, owner, warp, err := instances[0].cli.Asset( + exists, metadata, supply, owner, warp, err := instances[0].tcli.Asset( context.TODO(), asset1ID, ) @@ -834,8 +873,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { ginkgo.It("mint asset from wrong owner", func() { other, err := crypto.GeneratePrivateKey() gomega.Ω(err).Should(gomega.BeNil()) + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.MintAsset{ To: other.PublicKey(), @@ -854,7 +896,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(string(result.Output)). Should(gomega.ContainSubstring("wrong owner")) - exists, metadata, supply, owner, warp, err := instances[0].cli.Asset( + exists, metadata, supply, owner, warp, err := instances[0].tcli.Asset( context.TODO(), asset1ID, ) @@ -867,8 +909,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("burn new asset", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.BurnAsset{ Asset: asset1ID, @@ -883,14 +928,14 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance, err := instances[0].cli.Balance(context.TODO(), sender2, asset1ID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender2, asset1ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(10))) - balance, err = instances[0].cli.Balance(context.TODO(), sender, asset1ID) + balance, err = instances[0].tcli.Balance(context.TODO(), sender, asset1ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(0))) - exists, metadata, supply, owner, warp, err := instances[0].cli.Asset( + exists, metadata, supply, owner, warp, err := instances[0].tcli.Asset( context.TODO(), asset1ID, ) @@ -903,8 +948,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("burn missing asset", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.BurnAsset{ Asset: asset1ID, @@ -922,7 +970,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(string(result.Output)). Should(gomega.ContainSubstring("invalid balance")) - exists, metadata, supply, owner, warp, err := instances[0].cli.Asset( + exists, metadata, supply, owner, warp, err := instances[0].tcli.Asset( context.TODO(), asset1ID, ) @@ -968,8 +1016,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("reject max mint", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.MintAsset{ To: rsender2, @@ -988,14 +1039,14 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(string(result.Output)). Should(gomega.ContainSubstring("overflow")) - balance, err := instances[0].cli.Balance(context.TODO(), sender2, asset1ID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender2, asset1ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(10))) - balance, err = instances[0].cli.Balance(context.TODO(), sender, asset1ID) + balance, err = instances[0].tcli.Balance(context.TODO(), sender, asset1ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(0))) - exists, metadata, supply, owner, warp, err := instances[0].cli.Asset( + exists, metadata, supply, owner, warp, err := instances[0].tcli.Asset( context.TODO(), asset1ID, ) @@ -1008,8 +1059,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("modify an existing asset", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.ModifyAsset{ Asset: asset1ID, @@ -1025,14 +1079,14 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance, err := instances[0].cli.Balance(context.TODO(), sender2, asset1ID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender2, asset1ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(10))) - balance, err = instances[0].cli.Balance(context.TODO(), sender, asset1ID) + balance, err = instances[0].tcli.Balance(context.TODO(), sender, asset1ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(0))) - exists, metadata, supply, owner, warp, err := instances[0].cli.Asset( + exists, metadata, supply, owner, warp, err := instances[0].tcli.Asset( context.TODO(), asset1ID, ) @@ -1046,8 +1100,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { ginkgo.It("modify an asset that doesn't exist", func() { assetID := ids.GenerateTestID() + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.ModifyAsset{ Asset: assetID, @@ -1066,7 +1123,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(string(result.Output)). Should(gomega.ContainSubstring("asset missing")) - exists, _, _, _, _, err := instances[0].cli.Asset(context.TODO(), assetID) + exists, _, _, _, _, err := instances[0].tcli.Asset(context.TODO(), assetID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(exists).Should(gomega.BeFalse()) }) @@ -1105,8 +1162,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("mints another new asset (to self)", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.CreateAsset{ Metadata: asset2, @@ -1123,6 +1183,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err = instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.MintAsset{ To: rsender, @@ -1138,14 +1199,17 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance, err := instances[0].cli.Balance(context.TODO(), sender, asset2ID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender, asset2ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(10))) }) ginkgo.It("mints another new asset (to self) on another account", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.CreateAsset{ Metadata: asset3, @@ -1162,6 +1226,7 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { submit, _, _, err = instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.MintAsset{ To: rsender2, @@ -1177,14 +1242,17 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance, err := instances[0].cli.Balance(context.TODO(), sender2, asset3ID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender2, asset3ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(10))) }) ginkgo.It("create simple order (want 3, give 2)", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.CreateOrder{ In: asset3ID, @@ -1202,11 +1270,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance, err := instances[0].cli.Balance(context.TODO(), sender, asset2ID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender, asset2ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(6))) - orders, err := instances[0].cli.Orders(context.TODO(), actions.PairID(asset3ID, asset2ID)) + orders, err := instances[0].tcli.Orders(context.TODO(), actions.PairID(asset3ID, asset2ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] @@ -1218,8 +1286,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("create simple order with misaligned supply", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.CreateOrder{ In: asset2ID, @@ -1242,8 +1313,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("create simple order (want 2, give 3) tracked", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.CreateOrder{ In: asset2ID, @@ -1261,11 +1335,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance, err := instances[0].cli.Balance(context.TODO(), sender2, asset3ID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender2, asset3ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(5))) - orders, err := instances[0].cli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) + orders, err := instances[0].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] @@ -1277,8 +1351,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("create order with insufficient balance", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.CreateOrder{ In: asset2ID, @@ -1301,14 +1378,17 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("fill order with misaligned value", func() { - orders, err := instances[0].cli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) + orders, err := instances[0].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] owner, err := utils.ParseAddress(order.Owner) gomega.Ω(err).Should(gomega.BeNil()) + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.FillOrder{ Order: order.ID, @@ -1331,14 +1411,17 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("fill order with insufficient balance", func() { - orders, err := instances[0].cli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) + orders, err := instances[0].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] owner, err := utils.ParseAddress(order.Owner) gomega.Ω(err).Should(gomega.BeNil()) + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.FillOrder{ Order: order.ID, @@ -1361,14 +1444,17 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("fill order with sufficient balance", func() { - orders, err := instances[0].cli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) + orders, err := instances[0].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] owner, err := utils.ParseAddress(order.Owner) gomega.Ω(err).Should(gomega.BeNil()) + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.FillOrder{ Order: order.ID, @@ -1392,14 +1478,14 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(or.Out).Should(gomega.Equal(uint64(1))) gomega.Ω(or.Remaining).Should(gomega.Equal(uint64(4))) - balance, err := instances[0].cli.Balance(context.TODO(), sender, asset3ID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender, asset3ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(1))) - balance, err = instances[0].cli.Balance(context.TODO(), sender, asset2ID) + balance, err = instances[0].tcli.Balance(context.TODO(), sender, asset2ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(2))) - orders, err = instances[0].cli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) + orders, err = instances[0].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order = orders[0] @@ -1407,12 +1493,15 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("close order with wrong owner", func() { - orders, err := instances[0].cli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) + orders, err := instances[0].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.CloseOrder{ Order: order.ID, @@ -1432,12 +1521,15 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("close order", func() { - orders, err := instances[0].cli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) + orders, err := instances[0].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.CloseOrder{ Order: order.ID, @@ -1453,27 +1545,30 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { result := results[0] gomega.Ω(result.Success).Should(gomega.BeTrue()) - balance, err := instances[0].cli.Balance(context.TODO(), sender, asset3ID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender, asset3ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(1))) - balance, err = instances[0].cli.Balance(context.TODO(), sender, asset2ID) + balance, err = instances[0].tcli.Balance(context.TODO(), sender, asset2ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(2))) - balance, err = instances[0].cli.Balance(context.TODO(), sender2, asset3ID) + balance, err = instances[0].tcli.Balance(context.TODO(), sender2, asset3ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(9))) - balance, err = instances[0].cli.Balance(context.TODO(), sender2, asset2ID) + balance, err = instances[0].tcli.Balance(context.TODO(), sender2, asset2ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(4))) - orders, err = instances[0].cli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) + orders, err = instances[0].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(0)) }) ginkgo.It("create simple order (want 2, give 3) tracked from another account", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.CreateOrder{ In: asset2ID, @@ -1491,11 +1586,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(results).Should(gomega.HaveLen(1)) gomega.Ω(results[0].Success).Should(gomega.BeTrue()) - balance, err := instances[0].cli.Balance(context.TODO(), sender, asset3ID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender, asset3ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(0))) - orders, err := instances[0].cli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) + orders, err := instances[0].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] @@ -1507,14 +1602,17 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { }) ginkgo.It("fill order with more than enough value", func() { - orders, err := instances[0].cli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) + orders, err := instances[0].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(1)) order := orders[0] owner, err := utils.ParseAddress(order.Owner) gomega.Ω(err).Should(gomega.BeNil()) + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.FillOrder{ Order: order.ID, @@ -1538,20 +1636,20 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(or.Out).Should(gomega.Equal(uint64(1))) gomega.Ω(or.Remaining).Should(gomega.Equal(uint64(0))) - balance, err := instances[0].cli.Balance(context.TODO(), sender, asset3ID) + balance, err := instances[0].tcli.Balance(context.TODO(), sender, asset3ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(0))) - balance, err = instances[0].cli.Balance(context.TODO(), sender, asset2ID) + balance, err = instances[0].tcli.Balance(context.TODO(), sender, asset2ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(4))) - balance, err = instances[0].cli.Balance(context.TODO(), sender2, asset3ID) + balance, err = instances[0].tcli.Balance(context.TODO(), sender2, asset3ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(10))) - balance, err = instances[0].cli.Balance(context.TODO(), sender2, asset2ID) + balance, err = instances[0].tcli.Balance(context.TODO(), sender2, asset2ID) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(balance).Should(gomega.Equal(uint64(2))) - orders, err = instances[0].cli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) + orders, err = instances[0].tcli.Orders(context.TODO(), actions.PairID(asset2ID, asset3ID)) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(orders).Should(gomega.HaveLen(0)) }) @@ -1696,8 +1794,11 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) wm, err := warp.NewMessage(uwm, &warp.BitSetSignature{}) gomega.Ω(err).Should(gomega.BeNil()) + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, wm, &actions.ImportAsset{}, factory, @@ -1723,12 +1824,15 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { ginkgo.It("export native asset", func() { dest := ids.GenerateTestID() - loan, err := instances[0].cli.Loan(context.TODO(), ids.Empty, dest) + loan, err := instances[0].tcli.Loan(context.TODO(), ids.Empty, dest) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(loan).Should(gomega.Equal(uint64(0))) + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, tx, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.ExportAsset{ To: rsender, @@ -1761,14 +1865,17 @@ var _ = ginkgo.Describe("[Tx Processing]", func() { gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(result.WarpMessage).Should(gomega.Equal(wm)) - loan, err = instances[0].cli.Loan(context.TODO(), ids.Empty, dest) + loan, err = instances[0].tcli.Loan(context.TODO(), ids.Empty, dest) gomega.Ω(err).Should(gomega.BeNil()) gomega.Ω(loan).Should(gomega.Equal(uint64(110))) }) ginkgo.It("export native asset (invalid return)", func() { + parser, err := instances[0].tcli.Parser(context.Background()) + gomega.Ω(err).Should(gomega.BeNil()) submit, _, _, err := instances[0].cli.GenerateTransaction( context.Background(), + parser, nil, &actions.ExportAsset{ To: rsender, diff --git a/examples/tokenvm/tests/load/load_test.go b/examples/tokenvm/tests/load/load_test.go index c680b1671c..db725ea871 100644 --- a/examples/tokenvm/tests/load/load_test.go +++ b/examples/tokenvm/tests/load/load_test.go @@ -42,11 +42,12 @@ import ( "github.com/ava-labs/hypersdk/examples/tokenvm/actions" "github.com/ava-labs/hypersdk/examples/tokenvm/auth" - "github.com/ava-labs/hypersdk/examples/tokenvm/client" "github.com/ava-labs/hypersdk/examples/tokenvm/consts" "github.com/ava-labs/hypersdk/examples/tokenvm/controller" "github.com/ava-labs/hypersdk/examples/tokenvm/genesis" + trpc "github.com/ava-labs/hypersdk/examples/tokenvm/rpc" "github.com/ava-labs/hypersdk/examples/tokenvm/utils" + "github.com/ava-labs/hypersdk/rpc" ) const ( @@ -72,18 +73,18 @@ func init() { } type instance struct { - chainID ids.ID - nodeID ids.NodeID - - vm *vm.VM - toEngine chan common.Message - httpServer *httptest.Server - cli *client.Client // clients for embedded VMs - dbDir string - - parse []float64 - verify []float64 - accept []float64 + chainID ids.ID + nodeID ids.NodeID + vm *vm.VM + toEngine chan common.Message + JSONRPCServer *httptest.Server + TokenJSONRPCServer *httptest.Server + cli *rpc.JSONRPCClient // clients for embedded VMs + tcli *trpc.JSONRPCClient + dbDir string + parse []float64 + verify []float64 + accept []float64 } type account struct { @@ -264,25 +265,28 @@ var _ = ginkgo.BeforeSuite(func() { var hd map[string]*common.HTTPHandler hd, err = c.CreateHandlers(context.TODO()) gomega.Ω(err).Should(gomega.BeNil()) - httpServer := httptest.NewServer(hd[vm.Endpoint].Handler) - - c.ForceReady() + jsonRPCServer := httptest.NewServer(hd[rpc.JSONRPCEndpoint].Handler) + tjsonRPCServer := httptest.NewServer(hd[trpc.JSONRPCEndpoint].Handler) instances[i] = &instance{ - chainID: snowCtx.ChainID, - nodeID: snowCtx.NodeID, - vm: c, - toEngine: toEngine, - httpServer: httpServer, - cli: client.New(httpServer.URL), - - dbDir: dname, + chainID: snowCtx.ChainID, + nodeID: snowCtx.NodeID, + vm: c, + toEngine: toEngine, + JSONRPCServer: jsonRPCServer, + TokenJSONRPCServer: tjsonRPCServer, + cli: rpc.NewJSONRPCClient(jsonRPCServer.URL), + tcli: trpc.NewJSONRPCClient(tjsonRPCServer.URL, snowCtx.ChainID), + dbDir: dname, } + + // Force sync ready (to mimic bootstrapping from genesis) + c.ForceReady() } // Verify genesis allocations loaded correctly (do here otherwise test may // check during and it will be inaccurate) for _, inst := range instances { - cli := inst.cli + cli := inst.tcli g, err := cli.Genesis(context.Background()) gomega.Ω(err).Should(gomega.BeNil()) @@ -299,7 +303,8 @@ var _ = ginkgo.BeforeSuite(func() { var _ = ginkgo.AfterSuite(func() { for _, instance := range instances { - instance.httpServer.Close() + instance.JSONRPCServer.Close() + instance.TokenJSONRPCServer.Close() err := instance.vm.Shutdown(context.TODO()) gomega.Ω(err).Should(gomega.BeNil()) } diff --git a/go.mod b/go.mod index e99e8b27b3..740600aed0 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,14 @@ module github.com/ava-labs/hypersdk go 1.20 require ( - github.com/ava-labs/avalanchego v1.9.16 + github.com/ava-labs/avalanchego v1.10.0 github.com/cockroachdb/pebble v0.0.0-20230224221607-fccb83b60d5c github.com/golang/mock v1.6.0 github.com/gorilla/rpc v1.2.0 + github.com/gorilla/websocket v1.5.0 github.com/neilotoole/errgroup v0.1.6 - github.com/onsi/ginkgo/v2 v2.4.0 - github.com/prometheus/client_golang v1.13.0 + github.com/onsi/ginkgo/v2 v2.7.0 + github.com/prometheus/client_golang v1.14.0 github.com/stretchr/testify v1.8.1 go.opentelemetry.io/otel v1.11.2 go.opentelemetry.io/otel/exporters/zipkin v1.11.2 @@ -21,7 +22,7 @@ require ( ) require ( - github.com/DataDog/zstd v1.4.5 // indirect + github.com/DataDog/zstd v1.5.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd/btcutil v1.1.3 // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect @@ -37,22 +38,21 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0 // indirect - github.com/holiman/bloomfilter/v2 v2.0.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 // indirect github.com/klauspost/compress v1.15.15 // indirect - github.com/kr/pretty v0.2.1 // indirect + github.com/kr/pretty v0.3.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d // indirect + github.com/onsi/gomega v1.25.0 // indirect github.com/openzipkin/zipkin-go v0.4.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect + github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect - github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/supranational/blst v0.3.11-0.20220920110316-f72618070295 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect @@ -61,16 +61,17 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.8.0 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/term v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect gonum.org/v1/gonum v0.11.0 // indirect - google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect - google.golang.org/grpc v1.51.0 // indirect + google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 // indirect + google.golang.org/grpc v1.53.0-dev // indirect google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 89e29d1ca6..cac2e5db42 100644 --- a/go.sum +++ b/go.sum @@ -37,8 +37,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= -github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= -github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= +github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -52,8 +52,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/ava-labs/avalanchego v1.9.16 h1:JarxIn7gy4V9f1dBgUxubRRO6CrqY2MprOLGqEmk+Vg= -github.com/ava-labs/avalanchego v1.9.16/go.mod h1:Unm7ruhAvLSRP+7gIfwyHNf+wEehWLsFhY9yp10nDbw= +github.com/ava-labs/avalanchego v1.10.0 h1:Rn6Nyd62OkzQG5QpCgtCGVXtjuiaEzxV000kqG9aUIg= +github.com/ava-labs/avalanchego v1.10.0/go.mod h1:hTaSLGN4y/EmhmYd+yjUj9Lsm00q70V78jOYDdnLrgQ= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -255,14 +255,12 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0 h1:kr3j8iIMR4ywO/O0rvksXaJvauGGCMg2zAZIiNZ9uIQ= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0/go.mod h1:ummNFgdgLhhX7aIiy35vVmQNS0rWXknfPE0qe6fmFXg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 h1:1JYBfzqrWPcCclBwxFCPAou9n+q86mfnu7NAeHfte7A= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0/go.mod h1:YDZoGHuwE+ov0c8smSH49WLF3F2LaWnYYuDVd+EWrc0= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= -github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -307,8 +305,9 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -358,15 +357,16 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= -github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow= +github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= +github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= +github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/openzipkin/zipkin-go v0.4.1 h1:kNd/ST2yLLWhaWrkgchya40TJabe8Hioj9udfPcEO5A= github.com/openzipkin/zipkin-go v0.4.1/go.mod h1:qY0VqDSN1pOBN94dBc6w2GJlWLiovAyg7Qt6/I9HecM= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -383,14 +383,14 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y= -github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= @@ -406,6 +406,9 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= @@ -417,8 +420,6 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= @@ -488,12 +489,11 @@ go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06O go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -787,8 +787,8 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 h1:jmIfw8+gSvXcZSgaFAGyInDXeWzUhvYH57G/5GKMn70= +google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -806,8 +806,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.53.0-dev h1:Bi96+XIrXJLXPJUff19tRXb7mIijir7agn12zNMaPAg= +google.golang.org/grpc v1.53.0-dev/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -826,8 +826,9 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= @@ -846,7 +847,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/listeners/listeners.go b/listeners/listeners.go deleted file mode 100644 index d01c8d553d..0000000000 --- a/listeners/listeners.go +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package listeners - -import ( - "errors" - "sync" - - "github.com/ava-labs/avalanchego/ids" - - "github.com/ava-labs/hypersdk/chain" - "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/hypersdk/consts" - "github.com/ava-labs/hypersdk/emap" - "github.com/ava-labs/hypersdk/pubsub" -) - -const ( - DecisionMode byte = 0 - BlockMode byte = 1 -) - -type Transaction struct { - TxID ids.ID - Result *chain.Result - Err error -} - -type Listeners struct { - txL sync.Mutex - txListeners map[ids.ID]*pubsub.Connections - expiringTxs *emap.EMap[*chain.Transaction] // ensures all tx listeners are eventually responded to - s *pubsub.Server -} - -func New(s *pubsub.Server) *Listeners { - return &Listeners{ - txListeners: map[ids.ID]*pubsub.Connections{}, - expiringTxs: emap.NewEMap[*chain.Transaction](), - s: s, - } -} - -// Note: no need to have a tx listener removal, this will happen when all -// submitted transactions are cleared. -func (w *Listeners) AddTxListener(tx *chain.Transaction, c *pubsub.Connection) { - w.txL.Lock() - defer w.txL.Unlock() - - txID := tx.ID() - if _, ok := w.txListeners[txID]; !ok { - w.txListeners[txID] = pubsub.NewConnections() - } - w.txListeners[txID].Add(c) - w.expiringTxs.Add([]*chain.Transaction{tx}) -} - -// If never possible for a tx to enter mempool, call this -func (w *Listeners) RemoveTx(txID ids.ID, err error) { - w.txL.Lock() - defer w.txL.Unlock() - - w.removeTx(txID, err) -} - -func (w *Listeners) removeTx(txID ids.ID, err error) { - listeners, ok := w.txListeners[txID] - if !ok { - return - } - p := codec.NewWriter(consts.MaxInt) - PackRemovedTxMessage(p, txID, err) - w.s.Publish(txID[:], listeners) - delete(w.txListeners, txID) - // [expiringTxs] will be cleared eventually (does not support removal) -} - -func (w *Listeners) SetMinTx(t int64) { - w.txL.Lock() - defer w.txL.Unlock() - - expired := w.expiringTxs.SetMin(t) - for _, id := range expired { - w.removeTx(id, ErrExpired) - } -} - -func (w *Listeners) AcceptBlock(b *chain.StatelessBlock) { - p := codec.NewWriter(consts.MaxInt) - PackBlockMessageBytes(b, p) - // Publish accepted block to all block listeners - w.s.Publish(p.Bytes(), w.s.Conns()) - w.txL.Lock() - defer w.txL.Unlock() - results := b.Results() - for i, tx := range b.Txs { - p := codec.NewWriter(consts.MaxInt) - txID := tx.ID() - listeners, ok := w.txListeners[txID] - if !ok { - continue - } - // Publish to tx listener - PackAcceptedTxMessage(p, txID, results[i]) - w.s.Publish( - p.Bytes(), - listeners, - ) - delete(w.txListeners, txID) - // [expiringTxs] will be cleared eventually (does not support removal) - } -} - -// Could be a better place for these methods -// Packs an accepted block message -func PackAcceptedTxMessage(p *codec.Packer, txID ids.ID, result *chain.Result) { - p.PackByte(DecisionMode) - p.PackID(txID) - p.PackBool(false) - result.Marshal(p) -} - -// Packs a removed block message -func PackRemovedTxMessage(p *codec.Packer, txID ids.ID, err error) { - p.PackByte(DecisionMode) - p.PackID(txID) - p.PackBool(true) - p.PackString(err.Error()) -} - -// Unpacks a tx message from [msg]. Returns the txID, an error regarding the status -// of the tx, the result of the tx, and an error if there was a -// problem unpacking the message. -func UnpackTxMessage(msg []byte) (ids.ID, error, *chain.Result, error) { - p := codec.NewReader(msg, consts.MaxInt) - p.UnpackByte() - // read the txID from packer - var txID ids.ID - p.UnpackID(true, &txID) - if p.UnpackBool() { - err := p.UnpackString(true) - if p.Err() != nil { - return ids.Empty, nil, nil, p.Err() - } - // convert err_bytes to error - return ids.Empty, errors.New(err), nil, nil - } - // unpack the result - result, err := chain.UnmarshalResult(p) - if err != nil { - return ids.Empty, nil, nil, err - } - // packer had an error - if p.Err() != nil { - return ids.Empty, nil, nil, p.Err() - } - // should be empty - if !p.Empty() { - return ids.Empty, nil, nil, chain.ErrInvalidObject - } - return txID, nil, result, nil -} - -func PackBlockMessageBytes(b *chain.StatelessBlock, p *codec.Packer) { - p.PackByte(BlockMode) - // Pack the block bytes - p.PackBytes(b.Bytes()) - results, err := chain.MarshalResults(b.Results()) - if err != nil { - return - } - // Pack the results bytes - p.PackBytes(results) -} - -func UnpackBlockMessageBytes( - msg []byte, - parser chain.Parser, -) (*chain.StatefulBlock, []*chain.Result, error) { - // Read block - p := codec.NewReader(msg, consts.MaxInt) - p.UnpackByte() - var blkMsg []byte - p.UnpackBytes(-1, false, &blkMsg) - blk, err := chain.UnmarshalBlock(blkMsg, parser) - if err != nil { - return nil, nil, err - } - // Read results - var resultsMsg []byte - p.UnpackBytes(-1, true, &resultsMsg) - results, err := chain.UnmarshalResults(resultsMsg) - if err != nil { - return nil, nil, err - } - // packer had an error - if p.Err() != nil { - return nil, nil, p.Err() - } - return blk, results, nil -} diff --git a/pubsub/connections.go b/pubsub/connections.go index 7bdc03d22a..36e82cc157 100644 --- a/pubsub/connections.go +++ b/pubsub/connections.go @@ -59,3 +59,10 @@ func (c *Connections) Len() int { return c.conns.Len() } + +func (c *Connections) Peek() (*Connection, bool) { + c.lock.RLock() + defer c.lock.RUnlock() + + return c.conns.Peek() +} diff --git a/pubsub/consts.go b/pubsub/consts.go index e736b7c3a9..adc24307e3 100644 --- a/pubsub/consts.go +++ b/pubsub/consts.go @@ -17,5 +17,4 @@ const ( PingPeriod = (PongWait * 9) / 10 MaxMessageSize = 10 * units.KiB // bytes MaxPendingMessages = 1024 - ReadHeaderTimeout = 5 * time.Second ) diff --git a/pubsub/server.go b/pubsub/server.go index 14b8df5cfb..06b503aa2e 100644 --- a/pubsub/server.go +++ b/pubsub/server.go @@ -4,9 +4,7 @@ package pubsub import ( - "context" "net/http" - "sync" "sync/atomic" "time" @@ -32,8 +30,6 @@ type ServerConfig struct { PongWait time.Duration // Send pings to peer with this period. Must be less than pongWait. PingPeriod time.Duration - // ReadHeaderTimeout is the maximum duration for reading a request. - ReadHeaderTimeout time.Duration } func NewDefaultServerConfig() *ServerConfig { @@ -45,51 +41,39 @@ func NewDefaultServerConfig() *ServerConfig { WriteWait: WriteWait, PongWait: PongWait, PingPeriod: (9 * PongWait) / 10, - ReadHeaderTimeout: ReadHeaderTimeout, } } -// TODO: make this configurable -var upgrader = websocket.Upgrader{ - CheckOrigin: func(*http.Request) bool { - return true - }, -} - // Server maintains the set of active clients and sends messages to the clients. // // Connect to the server after starting using websocket.DefaultDialer.Dial(). type Server struct { - // The http server - s *http.Server - // The address to listen on - addr string - log logging.Logger - lock sync.RWMutex - // conns a set of all our connections - conns *Connections - // Callback function when server receives a message + log logging.Logger + config *ServerConfig callback Callback - // Config variables - config *ServerConfig + upgrader *websocket.Upgrader + conns *Connections } // New returns a new Server instance. The callback function [f] is called // by the server in response to messages if not nil. func New( - addr string, - r Callback, log logging.Logger, config *ServerConfig, + callback Callback, ) *Server { - upgrader.ReadBufferSize = config.ReadBufferSize - upgrader.WriteBufferSize = config.WriteBufferSize return &Server{ log: log, - addr: addr, - callback: r, - conns: NewConnections(), config: config, + callback: callback, + upgrader: &websocket.Upgrader{ + CheckOrigin: func(*http.Request) bool { + return true + }, + ReadBufferSize: config.ReadBufferSize, + WriteBufferSize: config.WriteBufferSize, + }, + conns: NewConnections(), } } @@ -98,9 +82,9 @@ func New( func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Upgrader.upgrade() is called to upgrade the HTTP connection. // No nead to set any headers so we pass nil as the last argument. - wsConn, err := upgrader.Upgrade(w, r, nil) + wsConn, err := s.upgrader.Upgrade(w, r, nil) if err != nil { - s.log.Debug("failed to upgrade", + s.log.Warn("failed to upgrade", zap.Error(err), ) return @@ -114,10 +98,11 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // Publish sends msg from [s] to [toConns]. -func (s *Server) Publish(msg []byte, toConns *Connections) { - for _, conn := range toConns.Conns() { - // check server has connection O(1) +func (s *Server) Publish(msg []byte, conns *Connections) []*Connection { + inactiveConnections := []*Connection{} + for _, conn := range conns.Conns() { if !s.conns.Has(conn) { + inactiveConnections = append(inactiveConnections, conn) continue } if !conn.Send(msg) { @@ -126,14 +111,12 @@ func (s *Server) Publish(msg []byte, toConns *Connections) { ) } } + return inactiveConnections } // addConnection adds [conn] to the servers connection set and starts go // routines for reading and writing messages for the connection. func (s *Server) addConnection(conn *Connection) { - s.lock.Lock() - defer s.lock.Unlock() - conn.active.Store(true) s.conns.Add(conn) @@ -146,24 +129,6 @@ func (s *Server) removeConnection(conn *Connection) { s.conns.Remove(conn) } -// Start starts the server. Returns an error if the server fails to start or -// when the server is stopped. -func (s *Server) Start() error { - s.lock.Lock() - s.s = &http.Server{ - Addr: s.addr, - Handler: s, - ReadHeaderTimeout: s.config.ReadHeaderTimeout, - } - s.lock.Unlock() - return s.s.ListenAndServe() -} - -// Shutdown shuts down the server and returns the associated error. -func (s *Server) Shutdown(c context.Context) error { - return s.s.Shutdown(c) -} - -func (s *Server) Conns() *Connections { +func (s *Server) Connections() *Connections { return s.conns } diff --git a/pubsub/server_test.go b/pubsub/server_test.go index 5fb06876ff..a116d31db0 100644 --- a/pubsub/server_test.go +++ b/pubsub/server_test.go @@ -51,16 +51,25 @@ func TestServerPublish(t *testing.T) { // Create a new logger for the test logger := logging.NoLog{} // Create a new pubsub server - server := New(dummyAddr, nil, logger, NewDefaultServerConfig()) + handler := New(logger, NewDefaultServerConfig(), nil) // Channels for ensuring if connections/server are closed closeConnection := make(chan bool) serverDone := make(chan struct{}) dummyMsg := "dummy_msg" // Go routine that listens on dummyAddress for connections + var server *http.Server go func() { defer close(serverDone) - err := server.Start() - require.ErrorIs(err, http.ErrServerClosed, "Incorrect error closing server.") + server = &http.Server{ + Addr: dummyAddr, + Handler: handler, + ReadHeaderTimeout: 30 * time.Second, + } + require.ErrorIs( + server.ListenAndServe(), + http.ErrServerClosed, + "Incorrect error closing server.", + ) }() // Connect to pubsub server u := url.URL{Scheme: "ws", Host: dummyAddr} @@ -70,9 +79,7 @@ func TestServerPublish(t *testing.T) { require.NoError(err, "Error connecting to the server.") defer resp.Body.Close() // Publish to subscribed connections - server.lock.Lock() - server.Publish([]byte(dummyMsg), server.conns) - server.lock.Unlock() + handler.Publish([]byte(dummyMsg), handler.Connections()) // Receive the message from the publish _, msg, err := webCon.ReadMessage() require.NoError(err, "Error receiveing message.") @@ -82,14 +89,10 @@ func TestServerPublish(t *testing.T) { go func() { webCon.Close() for { - server.lock.Lock() - len := server.conns.Len() - if len == 0 { - server.lock.Unlock() + if handler.conns.Len() == 0 { closeConnection <- true return } - server.lock.Unlock() time.Sleep(10 * time.Millisecond) } }() @@ -118,16 +121,24 @@ func TestServerRead(t *testing.T) { val: 10, } // Create a new pubsub server - server := New(dummyAddr, counter.dummyProcessTXCallback, - logger, NewDefaultServerConfig()) + handler := New(logger, NewDefaultServerConfig(), counter.dummyProcessTXCallback) // Channels for ensuring if connections/server are closed closeConnection := make(chan bool) serverDone := make(chan struct{}) // Go routine that listens on dummyAddress for connections + var server *http.Server go func() { defer close(serverDone) - err := server.Start() - require.ErrorIs(err, http.ErrServerClosed, "Incorrect error closing server.") + server = &http.Server{ + Addr: dummyAddr, + Handler: handler, + ReadHeaderTimeout: 30 * time.Second, + } + require.ErrorIs( + server.ListenAndServe(), + http.ErrServerClosed, + "Incorrect error closing server.", + ) }() // Connect to pubsub server u := url.URL{Scheme: "ws", Host: dummyAddr} @@ -149,14 +160,10 @@ func TestServerRead(t *testing.T) { go func() { webCon.Close() for { - server.lock.Lock() - len := server.conns.Len() - if len == 0 { - server.lock.Unlock() + if handler.conns.Len() == 0 { closeConnection <- true return } - server.lock.Unlock() time.Sleep(10 * time.Millisecond) } }() @@ -186,18 +193,25 @@ func TestServerPublishSpecific(t *testing.T) { val: 10, } // Create a new pubsub server - server := New(dummyAddr, counter.dummyProcessTXCallback, - logger, NewDefaultServerConfig()) - + handler := New(logger, NewDefaultServerConfig(), counter.dummyProcessTXCallback) // Channels for ensuring if connections/server are closed closeConnection := make(chan bool) serverDone := make(chan struct{}) dummyMsg := "dummy_msg" // Go routine that listens on dummyAddress for connections + var server *http.Server go func() { defer close(serverDone) - err := server.Start() - require.ErrorIs(err, http.ErrServerClosed, "Incorrect error closing server.") + server = &http.Server{ + Addr: dummyAddr, + Handler: handler, + ReadHeaderTimeout: 30 * time.Second, + } + require.ErrorIs( + server.ListenAndServe(), + http.ErrServerClosed, + "Incorrect error closing server.", + ) }() // Connect to pubsub server u := url.URL{Scheme: "ws", Host: dummyAddr} @@ -207,18 +221,14 @@ func TestServerPublishSpecific(t *testing.T) { require.NoError(err, "Error connecting to the server.") defer resp1.Body.Close() sendConns := NewConnections() - server.lock.Lock() - peekCon, _ := server.conns.conns.Peek() - server.lock.Unlock() + peekCon, _ := handler.conns.Peek() sendConns.Add(peekCon) webCon2, resp2, err := websocket.DefaultDialer.Dial(u.String(), nil) require.NoError(err, "Error connecting to the server.") defer resp2.Body.Close() - require.Equal(2, server.conns.Len(), "Server didn't add connection correctly.") + require.Equal(2, handler.conns.Len(), "Server didn't add connection correctly.") // Publish to subscribed connections - server.lock.Lock() - server.Publish([]byte(dummyMsg), sendConns) - server.lock.Unlock() + handler.Publish([]byte(dummyMsg), sendConns) go func() { // Receive the message from the publish _, msg, err := webCon1.ReadMessage() @@ -227,14 +237,10 @@ func TestServerPublishSpecific(t *testing.T) { require.Equal([]byte(dummyMsg), msg, "Message not as expected.") webCon1.Close() for { - server.lock.Lock() - len := server.conns.Len() - if len == 0 { - server.lock.Unlock() + if handler.conns.Len() == 0 { closeConnection <- true return } - server.lock.Unlock() time.Sleep(10 * time.Millisecond) } }() @@ -250,14 +256,10 @@ func TestServerPublishSpecific(t *testing.T) { require.True(netErr.Timeout(), "Error is not a timeout error") webCon2.Close() for { - server.lock.Lock() - len := server.conns.Len() - if len == 0 { - server.lock.Unlock() + if handler.conns.Len() == 0 { closeConnection <- true return } - server.lock.Unlock() time.Sleep(10 * time.Millisecond) } }() diff --git a/rpc/consts.go b/rpc/consts.go new file mode 100644 index 0000000000..a19051fa83 --- /dev/null +++ b/rpc/consts.go @@ -0,0 +1,10 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rpc + +const ( + Name = "hypersdk" + JSONRPCEndpoint = "/coreapi" + WebSocketEndpoint = "/corews" +) diff --git a/rpc/dependencies.go b/rpc/dependencies.go new file mode 100644 index 0000000000..6522949238 --- /dev/null +++ b/rpc/dependencies.go @@ -0,0 +1,37 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rpc + +import ( + "context" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/trace" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/hypersdk/chain" +) + +type VM interface { + ChainID() ids.ID + NetworkID() uint32 + SubnetID() ids.ID + Tracer() trace.Tracer + Logger() logging.Logger + Registry() (chain.ActionRegistry, chain.AuthRegistry) + Submit( + ctx context.Context, + verifySig bool, + txs []*chain.Transaction, + ) (errs []error) + LastAcceptedBlock() *chain.StatelessBlock + SuggestedFee(context.Context) (uint64, uint64, error) + GetOutgoingWarpMessage(ids.ID) (*warp.UnsignedMessage, error) + GetWarpSignatures(ids.ID) ([]*chain.WarpSignature, error) + CurrentValidators( + context.Context, + ) (map[ids.NodeID]*validators.GetValidatorOutput, map[string]struct{}) + GatherSignatures(context.Context, ids.ID, []byte) +} diff --git a/rpc/errors.go b/rpc/errors.go new file mode 100644 index 0000000000..fdcf331735 --- /dev/null +++ b/rpc/errors.go @@ -0,0 +1,11 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rpc + +import "errors" + +var ( + ErrExpired = errors.New("expired") + ErrMessageMissing = errors.New("message missing") +) diff --git a/client/helpers.go b/rpc/jsonrpc_client.go similarity index 61% rename from client/helpers.go rename to rpc/jsonrpc_client.go index 75c78faccc..bf5a90d6bb 100644 --- a/client/helpers.go +++ b/rpc/jsonrpc_client.go @@ -1,11 +1,12 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package client +package rpc import ( "context" "fmt" + "strings" "time" "github.com/ava-labs/avalanchego/ids" @@ -15,18 +16,153 @@ import ( "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "golang.org/x/exp/maps" + "github.com/ava-labs/hypersdk/chain" + "github.com/ava-labs/hypersdk/requester" "github.com/ava-labs/hypersdk/utils" - "golang.org/x/exp/maps" ) -const waitSleep = 500 * time.Millisecond +const ( + suggestedFeeCacheRefresh = 10 * time.Second + waitSleep = 500 * time.Millisecond +) + +type JSONRPCClient struct { + requester *requester.EndpointRequester + + networkID uint32 + subnetID ids.ID + chainID ids.ID + + lastSuggestedFee time.Time + unitPrice uint64 + blockCost uint64 +} + +func NewJSONRPCClient(uri string) *JSONRPCClient { + uri = strings.TrimSuffix(uri, "/") + uri += JSONRPCEndpoint + req := requester.New(uri, Name) + return &JSONRPCClient{requester: req} +} + +func (cli *JSONRPCClient) Ping(ctx context.Context) (bool, error) { + resp := new(PingReply) + err := cli.requester.SendRequest(ctx, + "ping", + nil, + resp, + ) + return resp.Success, err +} + +func (cli *JSONRPCClient) Network(ctx context.Context) (uint32, ids.ID, ids.ID, error) { + if cli.chainID != ids.Empty { + return cli.networkID, cli.subnetID, cli.chainID, nil + } + + resp := new(NetworkReply) + err := cli.requester.SendRequest( + ctx, + "network", + nil, + resp, + ) + if err != nil { + return 0, ids.Empty, ids.Empty, err + } + cli.networkID = resp.NetworkID + cli.subnetID = resp.SubnetID + cli.chainID = resp.ChainID + return resp.NetworkID, resp.SubnetID, resp.ChainID, nil +} + +func (cli *JSONRPCClient) Accepted(ctx context.Context) (ids.ID, uint64, int64, error) { + resp := new(LastAcceptedReply) + err := cli.requester.SendRequest( + ctx, + "lastAccepted", + nil, + resp, + ) + return resp.BlockID, resp.Height, resp.Timestamp, err +} + +func (cli *JSONRPCClient) SuggestedRawFee(ctx context.Context) (uint64, uint64, error) { + if time.Since(cli.lastSuggestedFee) < suggestedFeeCacheRefresh { + return cli.unitPrice, cli.blockCost, nil + } + + resp := new(SuggestedRawFeeReply) + err := cli.requester.SendRequest( + ctx, + "suggestedRawFee", + nil, + resp, + ) + if err != nil { + return 0, 0, err + } + cli.unitPrice = resp.UnitPrice + cli.blockCost = resp.BlockCost + // We update the time last in case there are concurrent requests being + // processed (we don't want them to get an inconsistent view). + cli.lastSuggestedFee = time.Now() + return resp.UnitPrice, resp.BlockCost, nil +} + +func (cli *JSONRPCClient) SubmitTx(ctx context.Context, d []byte) (ids.ID, error) { + resp := new(SubmitTxReply) + err := cli.requester.SendRequest( + ctx, + "submitTx", + &SubmitTxArgs{Tx: d}, + resp, + ) + return resp.TxID, err +} + +func (cli *JSONRPCClient) GetWarpSignatures( + ctx context.Context, + txID ids.ID, +) (*warp.UnsignedMessage, map[ids.NodeID]*validators.GetValidatorOutput, []*chain.WarpSignature, error) { + resp := new(GetWarpSignaturesReply) + if err := cli.requester.SendRequest( + ctx, + "getWarpSignatures", + &GetWarpSignaturesArgs{TxID: txID}, + resp, + ); err != nil { + return nil, nil, nil, err + } + // Ensure message is initialized + if err := resp.Message.Initialize(); err != nil { + return nil, nil, nil, err + } + m := map[ids.NodeID]*validators.GetValidatorOutput{} + for _, vdr := range resp.Validators { + vout := &validators.GetValidatorOutput{ + NodeID: vdr.NodeID, + Weight: vdr.Weight, + } + if len(vdr.PublicKey) > 0 { + pk, err := bls.PublicKeyFromBytes(vdr.PublicKey) + if err != nil { + return nil, nil, nil, err + } + vout.PublicKey = pk + } + m[vdr.NodeID] = vout + } + return resp.Message, m, resp.Signatures, nil +} type Modifier interface { Base(*chain.Base) } -func (cli *Client) GenerateTransaction( +func (cli *JSONRPCClient) GenerateTransaction( ctx context.Context, parser chain.Parser, wm *warp.Message, @@ -140,7 +276,7 @@ func getCanonicalValidatorSet( return vdrList, totalWeight, nil } -func (cli *Client) GenerateAggregateWarpSignature( +func (cli *JSONRPCClient) GenerateAggregateWarpSignature( ctx context.Context, txID ids.ID, ) (*warp.Message, uint64, uint64, error) { diff --git a/vm/handler.go b/rpc/jsonrpc_server.go similarity index 63% rename from vm/handler.go rename to rpc/jsonrpc_server.go index 105c75ba09..f2510402e9 100644 --- a/vm/handler.go +++ b/rpc/jsonrpc_server.go @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package vm +package rpc import ( "context" @@ -17,24 +17,20 @@ import ( "go.uber.org/zap" ) -const ( - Endpoint = "/rpc" -) - -type Handler struct { - vm *VM +type JSONRPCServer struct { + vm VM } -func (vm *VM) Handler() *Handler { - return &Handler{vm} +func NewJSONRPCServer(vm VM) *JSONRPCServer { + return &JSONRPCServer{vm} } type PingReply struct { Success bool `json:"success"` } -func (h *Handler) Ping(_ *http.Request, _ *struct{}, reply *PingReply) (err error) { - h.vm.snowCtx.Log.Info("ping") +func (j *JSONRPCServer) Ping(_ *http.Request, _ *struct{}, reply *PingReply) (err error) { + j.vm.Logger().Info("ping") reply.Success = true return nil } @@ -45,10 +41,10 @@ type NetworkReply struct { ChainID ids.ID `json:"chainId"` } -func (h *Handler) Network(_ *http.Request, _ *struct{}, reply *NetworkReply) (err error) { - reply.NetworkID = h.vm.snowCtx.NetworkID - reply.SubnetID = h.vm.snowCtx.SubnetID - reply.ChainID = h.vm.snowCtx.ChainID +func (j *JSONRPCServer) Network(_ *http.Request, _ *struct{}, reply *NetworkReply) (err error) { + reply.NetworkID = j.vm.NetworkID() + reply.SubnetID = j.vm.SubnetID() + reply.ChainID = j.vm.ChainID() return nil } @@ -60,12 +56,17 @@ type SubmitTxReply struct { TxID ids.ID `json:"txId"` } -func (h *Handler) SubmitTx(req *http.Request, args *SubmitTxArgs, reply *SubmitTxReply) error { - ctx, span := h.vm.Tracer().Start(req.Context(), "Handler.SubmitTx") +func (j *JSONRPCServer) SubmitTx( + req *http.Request, + args *SubmitTxArgs, + reply *SubmitTxReply, +) error { + ctx, span := j.vm.Tracer().Start(req.Context(), "JSONRPCServer.SubmitTx") defer span.End() + actionRegistry, authRegistry := j.vm.Registry() rtx := codec.NewReader(args.Tx, chain.NetworkSizeLimit) // will likely be much smaller than this - tx, err := chain.UnmarshalTx(rtx, h.vm.actionRegistry, h.vm.authRegistry) + tx, err := chain.UnmarshalTx(rtx, actionRegistry, authRegistry) if err != nil { return fmt.Errorf("%w: unable to unmarshal on public service", err) } @@ -77,7 +78,7 @@ func (h *Handler) SubmitTx(req *http.Request, args *SubmitTxArgs, reply *SubmitT } txID := tx.ID() reply.TxID = txID - return h.vm.Submit(ctx, false, []*chain.Transaction{tx})[0] + return j.vm.Submit(ctx, false, []*chain.Transaction{tx})[0] } type LastAcceptedReply struct { @@ -86,8 +87,8 @@ type LastAcceptedReply struct { Timestamp int64 `json:"timestamp"` } -func (h *Handler) LastAccepted(_ *http.Request, _ *struct{}, reply *LastAcceptedReply) error { - blk := h.vm.lastAccepted +func (j *JSONRPCServer) LastAccepted(_ *http.Request, _ *struct{}, reply *LastAcceptedReply) error { + blk := j.vm.LastAcceptedBlock() reply.Height = blk.Hght reply.BlockID = blk.ID() reply.Timestamp = blk.Tmstmp @@ -99,15 +100,15 @@ type SuggestedRawFeeReply struct { BlockCost uint64 `json:"blockCost"` } -func (h *Handler) SuggestedRawFee( +func (j *JSONRPCServer) SuggestedRawFee( req *http.Request, _ *struct{}, reply *SuggestedRawFeeReply, ) error { - ctx, span := h.vm.Tracer().Start(req.Context(), "Handler.SuggestedRawFee") + ctx, span := j.vm.Tracer().Start(req.Context(), "JSONRPCServer.SuggestedRawFee") defer span.End() - unitPrice, blockCost, err := h.vm.SuggestedFee(ctx) + unitPrice, blockCost, err := j.vm.SuggestedFee(ctx) if err != nil { return err } @@ -116,15 +117,6 @@ func (h *Handler) SuggestedRawFee( return nil } -type PortReply struct { - Port uint16 `json:"port"` -} - -func (h *Handler) StreamingPort(_ *http.Request, _ *struct{}, reply *PortReply) error { - reply.Port = h.vm.config.GetStreamingPort() - return nil -} - type GetWarpSignaturesArgs struct { TxID ids.ID `json:"txID"` } @@ -136,20 +128,20 @@ type WarpValidator struct { } type GetWarpSignaturesReply struct { - Validators []*WarpValidator `json:"validators"` - Message *warp.UnsignedMessage `json:"message"` - Signatures []*WarpSignature `json:"signatures"` + Validators []*WarpValidator `json:"validators"` + Message *warp.UnsignedMessage `json:"message"` + Signatures []*chain.WarpSignature `json:"signatures"` } -func (h *Handler) GetWarpSignatures( +func (j *JSONRPCServer) GetWarpSignatures( req *http.Request, args *GetWarpSignaturesArgs, reply *GetWarpSignaturesReply, ) error { - _, span := h.vm.Tracer().Start(req.Context(), "Handler.GetWarpSignatures") + _, span := j.vm.Tracer().Start(req.Context(), "JSONRPCServer.GetWarpSignatures") defer span.End() - message, err := h.vm.GetOutgoingWarpMessage(args.TxID) + message, err := j.vm.GetOutgoingWarpMessage(args.TxID) if err != nil { return err } @@ -157,15 +149,15 @@ func (h *Handler) GetWarpSignatures( return ErrMessageMissing } - signatures, err := h.vm.GetWarpSignatures(args.TxID) + signatures, err := j.vm.GetWarpSignatures(args.TxID) if err != nil { return err } // Ensure we only return valid signatures - validSignatures := []*WarpSignature{} + validSignatures := []*chain.WarpSignature{} warpValidators := []*WarpValidator{} - validators, publicKeys := h.vm.proposerMonitor.Validators(req.Context()) + validators, publicKeys := j.vm.CurrentValidators(req.Context()) for _, sig := range signatures { if _, ok := publicKeys[string(sig.PublicKey)]; !ok { continue @@ -185,7 +177,7 @@ func (h *Handler) GetWarpSignatures( // Optimistically request that we gather signatures if we don't have all of them if len(validSignatures) < len(publicKeys) { - h.vm.snowCtx.Log.Info( + j.vm.Logger().Info( "fetching missing signatures", zap.Stringer("txID", args.TxID), zap.Int( @@ -195,7 +187,7 @@ func (h *Handler) GetWarpSignatures( zap.Int("valid", len(validSignatures)), zap.Int("current public key count", len(publicKeys)), ) - h.vm.warpManager.GatherSignatures(context.TODO(), args.TxID, message.Bytes()) + j.vm.GatherSignatures(context.TODO(), args.TxID, message.Bytes()) } reply.Message = message diff --git a/rpc/utils.go b/rpc/utils.go new file mode 100644 index 0000000000..ecd559262d --- /dev/null +++ b/rpc/utils.go @@ -0,0 +1,30 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rpc + +import ( + "net/http" + + "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/utils/json" + "github.com/gorilla/rpc/v2" +) + +func NewJSONRPCHandler( + name string, + service interface{}, + lockOption common.LockOption, +) (*common.HTTPHandler, error) { + server := rpc.NewServer() + server.RegisterCodec(json.NewCodec(), "application/json") + server.RegisterCodec(json.NewCodec(), "application/json;charset=UTF-8") + if err := server.RegisterService(service, name); err != nil { + return nil, err + } + return &common.HTTPHandler{LockOptions: lockOption, Handler: server}, nil +} + +func NewWebSocketHandler(server http.Handler) *common.HTTPHandler { + return &common.HTTPHandler{LockOptions: common.NoLock, Handler: server} +} diff --git a/rpc/websocket_client.go b/rpc/websocket_client.go new file mode 100644 index 0000000000..4534ccbfe1 --- /dev/null +++ b/rpc/websocket_client.go @@ -0,0 +1,121 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rpc + +import ( + "context" + "strings" + "sync" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" + "github.com/ava-labs/hypersdk/utils" + "github.com/gorilla/websocket" +) + +const pendingChanSize = 8_192 + +type WebSocketClient struct { + conn *websocket.Conn + cl sync.Once + + pendingBlocks chan []byte + pendingTxs chan []byte + + done chan struct{} + err error +} + +// NewWebSocketClient creates a new client for the decision rpc server. +// Dials into the server at [uri] and returns a client. +func NewWebSocketClient(uri string) (*WebSocketClient, error) { + uri = strings.ReplaceAll(uri, "http://", "ws://") + uri = strings.ReplaceAll(uri, "https://", "wss://") + if !strings.HasPrefix(uri, "ws") { // fallback to default usage + uri = "ws://" + uri + } + uri = strings.TrimSuffix(uri, "/") + uri += WebSocketEndpoint + conn, resp, err := websocket.DefaultDialer.Dial(uri, nil) + if err != nil { + return nil, err + } + resp.Body.Close() + wc := &WebSocketClient{ + conn: conn, + pendingBlocks: make(chan []byte, pendingChanSize), + pendingTxs: make(chan []byte, pendingChanSize), + done: make(chan struct{}), + } + go func() { + for { + _, msg, err := conn.ReadMessage() + if err != nil { + wc.err = err + close(wc.done) + return + } + if len(msg) == 0 { + utils.Outf("{{orange}}got empty message{{/}}\n") + continue + } + tmsg := msg[1:] + switch msg[0] { + case BlockMode: + wc.pendingBlocks <- tmsg + case TxMode: + wc.pendingTxs <- tmsg + default: + utils.Outf("{{orange}}unexpected message mode:{{/}} %x\n", msg[0]) + continue + } + } + }() + return wc, nil +} + +func (c *WebSocketClient) RegisterBlocks() error { + return c.conn.WriteMessage(websocket.BinaryMessage, []byte{BlockMode}) +} + +// Listen listens for block messages from the streaming server. +func (c *WebSocketClient) ListenBlock( + ctx context.Context, + parser chain.Parser, +) (*chain.StatefulBlock, []*chain.Result, error) { + select { + case msg := <-c.pendingBlocks: + return UnpackBlockMessage(msg, parser) + case <-c.done: + return nil, nil, c.err + case <-ctx.Done(): + return nil, nil, ctx.Err() + } +} + +// IssueTx sends [tx] to the streaming rpc server. +func (c *WebSocketClient) RegisterTx(tx *chain.Transaction) error { + return c.conn.WriteMessage(websocket.BinaryMessage, append([]byte{TxMode}, tx.Bytes()...)) +} + +// ListenForTx listens for responses from the streamingServer. +func (c *WebSocketClient) ListenTx(ctx context.Context) (ids.ID, error, *chain.Result, error) { + select { + case msg := <-c.pendingTxs: + return UnpackTxMessage(msg) + case <-c.done: + return ids.Empty, nil, nil, c.err + case <-ctx.Done(): + return ids.Empty, nil, nil, ctx.Err() + } +} + +// Close closes [c]'s connection to the decision rpc server. +func (c *WebSocketClient) Close() error { + var err error + c.cl.Do(func() { + err = c.conn.Close() + }) + return err +} diff --git a/rpc/websocket_packer.go b/rpc/websocket_packer.go new file mode 100644 index 0000000000..41c0492739 --- /dev/null +++ b/rpc/websocket_packer.go @@ -0,0 +1,92 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rpc + +import ( + "errors" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/hypersdk/chain" + "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/consts" +) + +const ( + BlockMode byte = 0 + TxMode byte = 1 +) + +func PackBlockMessage(b *chain.StatelessBlock) ([]byte, error) { + p := codec.NewWriter(consts.MaxInt) + p.PackBytes(b.Bytes()) + results, err := chain.MarshalResults(b.Results()) + if err != nil { + return nil, err + } + p.PackBytes(results) + return p.Bytes(), p.Err() +} + +func UnpackBlockMessage( + msg []byte, + parser chain.Parser, +) (*chain.StatefulBlock, []*chain.Result, error) { + p := codec.NewReader(msg, consts.MaxInt) + var blkMsg []byte + p.UnpackBytes(-1, true, &blkMsg) + blk, err := chain.UnmarshalBlock(blkMsg, parser) + if err != nil { + return nil, nil, err + } + var resultsMsg []byte + p.UnpackBytes(-1, true, &resultsMsg) + results, err := chain.UnmarshalResults(resultsMsg) + if err != nil { + return nil, nil, err + } + if !p.Empty() { + return nil, nil, chain.ErrInvalidObject + } + return blk, results, p.Err() +} + +// Could be a better place for these methods +// Packs an accepted block message +func PackAcceptedTxMessage(txID ids.ID, result *chain.Result) ([]byte, error) { + p := codec.NewWriter(consts.MaxInt) + p.PackID(txID) + p.PackBool(false) + result.Marshal(p) + return p.Bytes(), p.Err() +} + +// Packs a removed block message +func PackRemovedTxMessage(txID ids.ID, err error) ([]byte, error) { + p := codec.NewWriter(consts.MaxInt) + p.PackID(txID) + p.PackBool(true) + p.PackString(err.Error()) + return p.Bytes(), p.Err() +} + +// Unpacks a tx message from [msg]. Returns the txID, an error regarding the status +// of the tx, the result of the tx, and an error if there was a +// problem unpacking the message. +func UnpackTxMessage(msg []byte) (ids.ID, error, *chain.Result, error) { + p := codec.NewReader(msg, consts.MaxInt) + var txID ids.ID + p.UnpackID(true, &txID) + if p.UnpackBool() { + err := p.UnpackString(true) + return ids.Empty, errors.New(err), nil, p.Err() + } + result, err := chain.UnmarshalResult(p) + if err != nil { + return ids.Empty, nil, nil, err + } + if !p.Empty() { + return ids.Empty, nil, nil, chain.ErrInvalidObject + } + return txID, nil, result, p.Err() +} diff --git a/rpc/websocket_server.go b/rpc/websocket_server.go new file mode 100644 index 0000000000..db2b63d16b --- /dev/null +++ b/rpc/websocket_server.go @@ -0,0 +1,189 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rpc + +import ( + "context" + "sync" + + "github.com/ava-labs/avalanchego/ids" + "go.uber.org/zap" + + "github.com/ava-labs/hypersdk/chain" + "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/emap" + "github.com/ava-labs/hypersdk/pubsub" +) + +type WebSocketServer struct { + s *pubsub.Server + + blockListeners *pubsub.Connections + + txL sync.Mutex + txListeners map[ids.ID]*pubsub.Connections + expiringTxs *emap.EMap[*chain.Transaction] // ensures all tx listeners are eventually responded to +} + +func NewWebSocketServer() *WebSocketServer { + return &WebSocketServer{ + blockListeners: pubsub.NewConnections(), + txListeners: map[ids.ID]*pubsub.Connections{}, + expiringTxs: emap.NewEMap[*chain.Transaction](), + } +} + +func (w *WebSocketServer) SetBackend(s *pubsub.Server) { + w.s = s +} + +// Note: no need to have a tx listener removal, this will happen when all +// submitted transactions are cleared. +func (w *WebSocketServer) AddTxListener(tx *chain.Transaction, c *pubsub.Connection) { + w.txL.Lock() + defer w.txL.Unlock() + + // TODO: limit max number of tx listeners a single connection can create + txID := tx.ID() + if _, ok := w.txListeners[txID]; !ok { + w.txListeners[txID] = pubsub.NewConnections() + } + w.txListeners[txID].Add(c) + w.expiringTxs.Add([]*chain.Transaction{tx}) +} + +// If never possible for a tx to enter mempool, call this +func (w *WebSocketServer) RemoveTx(txID ids.ID, err error) error { + w.txL.Lock() + defer w.txL.Unlock() + + return w.removeTx(txID, err) +} + +func (w *WebSocketServer) removeTx(txID ids.ID, err error) error { + listeners, ok := w.txListeners[txID] + if !ok { + return nil + } + bytes, err := PackRemovedTxMessage(txID, err) + if err != nil { + return err + } + w.s.Publish(append([]byte{TxMode}, bytes...), listeners) + delete(w.txListeners, txID) + // [expiringTxs] will be cleared eventually (does not support removal) + return nil +} + +func (w *WebSocketServer) SetMinTx(t int64) error { + w.txL.Lock() + defer w.txL.Unlock() + + expired := w.expiringTxs.SetMin(t) + for _, id := range expired { + if err := w.removeTx(id, ErrExpired); err != nil { + return err + } + } + return nil +} + +func (w *WebSocketServer) AcceptBlock(b *chain.StatelessBlock) error { + bytes, err := PackBlockMessage(b) + if err != nil { + return err + } + inactiveConnection := w.s.Publish(append([]byte{BlockMode}, bytes...), w.blockListeners) + for _, conn := range inactiveConnection { + w.blockListeners.Remove(conn) + } + + w.txL.Lock() + defer w.txL.Unlock() + results := b.Results() + for i, tx := range b.Txs { + txID := tx.ID() + listeners, ok := w.txListeners[txID] + if !ok { + continue + } + // Publish to tx listener + bytes, err := PackAcceptedTxMessage(txID, results[i]) + if err != nil { + return err + } + w.s.Publish(append([]byte{TxMode}, bytes...), listeners) + delete(w.txListeners, txID) + // [expiringTxs] will be cleared eventually (does not support removal) + } + return nil +} + +func (w *WebSocketServer) MessageCallback(vm VM) pubsub.Callback { + // Assumes controller is initialized before this is called + var ( + actionRegistry, authRegistry = vm.Registry() + tracer = vm.Tracer() + log = vm.Logger() + ) + + return func(msgBytes []byte, c *pubsub.Connection) { + ctx, span := tracer.Start(context.Background(), "WebSocketServer.Callback") + defer span.End() + + // Check empty messages + if len(msgBytes) == 0 { + log.Error("failed to unmarshal msg", + zap.Int("len", len(msgBytes)), + ) + return + } + + // TODO: convert into a router that can be re-used in custom WS + // implementations + switch msgBytes[0] { + case BlockMode: + w.blockListeners.Add(c) + log.Debug("added block listener") + case TxMode: + msgBytes = msgBytes[1:] + // Unmarshal TX + p := codec.NewReader(msgBytes, chain.NetworkSizeLimit) // will likely be much smaller + tx, err := chain.UnmarshalTx(p, actionRegistry, authRegistry) + if err != nil { + log.Error("failed to unmarshal tx", + zap.Int("len", len(msgBytes)), + zap.Error(err), + ) + return + } + + // Verify tx + sigVerify := tx.AuthAsyncVerify() + if err := sigVerify(); err != nil { + log.Error("failed to verify sig", + zap.Error(err), + ) + return + } + w.AddTxListener(tx, c) + + // Submit will remove from [txWaiters] if it is not added + txID := tx.ID() + if err := vm.Submit(ctx, false, []*chain.Transaction{tx})[0]; err != nil { + log.Error("failed to submit tx", + zap.Stringer("txID", txID), + zap.Error(err), + ) + return + } + log.Debug("submitted tx", zap.Stringer("id", txID)) + default: + log.Error("unexpected message type", + zap.Int("len", len(msgBytes)), + zap.Uint8("mode", msgBytes[0]), + ) + } + } +} diff --git a/tstate/tstate.go b/tstate/tstate.go index f839a0e3fb..76ef7ccd7b 100644 --- a/tstate/tstate.go +++ b/tstate/tstate.go @@ -14,24 +14,17 @@ import ( oteltrace "go.opentelemetry.io/otel/trace" ) -type opAction int - -const ( - read opAction = iota - insert - remove -) - type op struct { - action opAction - k []byte - v []byte - pastV *tempStorage + k string + + pastExists bool + pastV []byte + pastChanged bool } type tempStorage struct { - v []byte - fromDB bool + v []byte + removed bool } type cacheItem struct { @@ -41,18 +34,14 @@ type cacheItem struct { // TState defines a struct for storing temporary state. type TState struct { - // We use pointers here because tempStorage objects may be added/removed from - // ops frequently. It is more efficient to avoid reallocating state each time - // this happens. - storage map[string]*tempStorage - changedKeys map[string]bool // Stores if key in [storage] was ever changed. - - fetchCache map[string]*cacheItem // in case we evict and want to re-fetch + changedKeys map[string]*tempStorage + fetchCache map[string]*cacheItem // in case we evict and want to re-fetch // We don't differentiate between read and write scope because it is very // uncommon for a user to write something without first reading what is // there. - scope [][]byte // Stores a list of managed keys in the TState struct. + scope [][]byte // stores a list of managed keys in the TState struct + scopeStorage map[string][]byte // Ops is a record of all operations performed on [TState]. Tracking // operations allows for reverting state to a certain point-in-time. @@ -61,10 +50,9 @@ type TState struct { // New returns a new instance of TState. Initializes the storage and changedKeys // maps to have an initial size of [storageSize] and [changedSize] respectively. -func New(storageSize int, changedSize int) *TState { +func New(changedSize int) *TState { return &TState{ - storage: make(map[string]*tempStorage, storageSize), - changedKeys: make(map[string]bool, changedSize), + changedKeys: make(map[string]*tempStorage, changedSize), fetchCache: map[string]*cacheItem{}, @@ -79,22 +67,38 @@ func (ts *TState) GetValue(ctx context.Context, key []byte) ([]byte, error) { if !ts.checkScope(ctx, key) { return nil, ErrKeyNotSpecified } - v, ok := ts.storage[string(key)] - if ok { - return v.v, nil + k := string(key) + v, _, exists := ts.getValue(ctx, k) + if !exists { + return nil, database.ErrNotFound + } + return v, nil +} + +func (ts *TState) getValue(_ context.Context, key string) ([]byte, bool, bool) { + if v, ok := ts.changedKeys[key]; ok { + if v.removed { + return nil, true, false + } + return v.v, true, true + } + v, ok := ts.scopeStorage[key] + if !ok { + return nil, false, false } - return nil, database.ErrNotFound + return v, false, true } // FetchAndSetScope updates ts to include the [db] values associated with [keys]. // FetchAndSetScope then sets the scope of ts to [keys]. If a key exists in // ts.fetchCache set the key's value to the value from cache. -func (ts *TState) FetchAndSetScope(ctx context.Context, db Database, keys [][]byte) error { +func (ts *TState) FetchAndSetScope(ctx context.Context, keys [][]byte, db Database) error { + ts.scopeStorage = map[string][]byte{} for _, key := range keys { k := string(key) if val, ok := ts.fetchCache[k]; ok { if val.Exists { - ts.SetStorage(ctx, key, val.Value) + ts.scopeStorage[k] = val.Value } continue } @@ -107,38 +111,16 @@ func (ts *TState) FetchAndSetScope(ctx context.Context, db Database, keys [][]by return err } ts.fetchCache[k] = &cacheItem{Value: v, Exists: true} - ts.SetStorage(ctx, key, v) + ts.scopeStorage[k] = v } - ts.SetScope(ctx, keys) + ts.scope = keys return nil } -// SetStorage sets ts.storage[key] = {value, true}. Does not add to storage if -// ts already stores a mapping with key, or that key was previously modified. -func (ts *TState) SetStorage(_ context.Context, key []byte, value []byte) { - k := string(key) - if _, ok := ts.storage[k]; ok { - // Don't double store info (2 txs could've reference) - return - } - if _, ok := ts.changedKeys[k]; ok { - // Don't overwrite if previously modified (tx could've deleted value - // previously) - return - } - - // Populate rollback (note, we only care if an item was placed in storage - // initially) - ts.ops = append(ts.ops, &op{ - action: read, - k: key, - }) - ts.storage[k] = &tempStorage{value, true} -} - // SetReadScope sets the readscope of ts to [keys]. -func (ts *TState) SetScope(_ context.Context, keys [][]byte) { +func (ts *TState) SetScope(_ context.Context, keys [][]byte, storage map[string][]byte) { ts.scope = keys + ts.scopeStorage = storage } // checkScope returns whether [k] is in ts.readScope. @@ -158,17 +140,14 @@ func (ts *TState) Insert(ctx context.Context, key []byte, value []byte) error { return ErrKeyNotSpecified } k := string(key) - - // Populate rollback + past, changed, exists := ts.getValue(ctx, k) ts.ops = append(ts.ops, &op{ - action: insert, - k: key, - v: value, - pastV: ts.storage[k], + k: k, + pastExists: exists, + pastV: past, + pastChanged: changed, }) - - ts.storage[k] = &tempStorage{value, false} - ts.changedKeys[k] = true + ts.changedKeys[k] = &tempStorage{value, false} return nil } @@ -178,16 +157,17 @@ func (ts *TState) Remove(ctx context.Context, key []byte) error { return ErrKeyNotSpecified } k := string(key) - - // Populate rollback + past, changed, exists := ts.getValue(ctx, k) + if !exists { + return nil + } ts.ops = append(ts.ops, &op{ - action: remove, - k: key, - pastV: ts.storage[k], + k: k, + pastExists: true, + pastV: past, + pastChanged: changed, }) - - delete(ts.storage, k) - ts.changedKeys[k] = false + ts.changedKeys[k] = &tempStorage{nil, true} return nil } @@ -196,41 +176,25 @@ func (ts *TState) OpIndex() int { return len(ts.ops) } +func (ts *TState) PendingChanges() int { + return len(ts.changedKeys) +} + // Rollback restores the TState to before the ts.op[restorePoint] operation. func (ts *TState) Rollback(_ context.Context, restorePoint int) { for i := len(ts.ops) - 1; i >= restorePoint; i-- { op := ts.ops[i] - k := string(op.k) - switch op.action { - case read: - delete(ts.storage, k) - delete(ts.changedKeys, k) - case insert: - if pv := op.pastV; pv != nil { - // Key previously inserted - if pv.fromDB { - delete(ts.changedKeys, k) - } - ts.storage[k] = pv - } else { - // Key inserted for the first time - delete(ts.storage, k) - delete(ts.changedKeys, k) - } - case remove: - if pv := op.pastV; pv != nil { - // Key previously inserted - if pv.fromDB { - delete(ts.changedKeys, k) - } - ts.storage[k] = pv - } else { - // Key deleted for the first time - delete(ts.changedKeys, k) - } - default: - panic("invalid op") + // insert: Modified key for the first time + // + // remove: Removed key that was modified for first time in run + if !op.pastChanged { + delete(ts.changedKeys, op.k) + continue } + // insert: Modified key for the nth time + // + // remove: Removed key that was previously modified in run + ts.changedKeys[op.k] = &tempStorage{op.pastV, !op.pastExists} } ts.ops = ts.ops[:restorePoint] } @@ -250,16 +214,15 @@ func (ts *TState) WriteChanges( ) defer span.End() - for key, added := range ts.changedKeys { - if added { - v := ts.storage[key] - if err := db.Insert(ctx, []byte(key), v.v); err != nil { - return err - } - } else { - if err := db.Remove(ctx, []byte(key)); err != nil { + for key, tstorage := range ts.changedKeys { + if !tstorage.removed { + if err := db.Insert(ctx, []byte(key), tstorage.v); err != nil { return err } + continue + } + if err := db.Remove(ctx, []byte(key)); err != nil { + return err } } return nil diff --git a/tstate/tstate_test.go b/tstate/tstate_test.go index 50d05304cc..4bb54919ea 100644 --- a/tstate/tstate_test.go +++ b/tstate/tstate_test.go @@ -41,70 +41,19 @@ func (db *TestDB) Insert(_ context.Context, key []byte, value []byte) error { } func (db *TestDB) Remove(_ context.Context, key []byte) error { - if _, ok := db.storage[string(key)]; !ok { - return database.ErrNotFound - } delete(db.storage, string(key)) return nil } -func TestSetStorage(t *testing.T) { - require := require.New(t) - ctx := context.TODO() - - ts := New(10, 10) - // Adds key/value - ts.SetStorage(ctx, TestKey, TestVal) - require.Equal(1, ts.OpIndex(), "Operation not added.") - // require.False(ts.storage[string(TestKey)].fromDB, "Value not from a DB.") - require.Equal(TestVal, ts.storage[string(TestKey)].v, "Value was not saved correctly.") -} - -func TestSetStorageKeyExists(t *testing.T) { - require := require.New(t) - ctx := context.TODO() - ts := New(10, 10) - // Adds key/value - ts.SetStorage(ctx, TestKey, TestVal) - require.Equal(1, ts.OpIndex(), "Operation not added.") - require.Equal(TestVal, ts.storage[string(TestKey)].v, "Value was not saved correctly.") - // Set a value with a different key. - ts.SetStorage(ctx, TestKey, []byte("DifferentValue")) - require.Equal(1, ts.OpIndex(), "Read operation was not added.") - require.Equal(TestVal, ts.storage[string(TestKey)].v, "Value was not saved correctly.") -} - -func TestSetStorageKeyModified(t *testing.T) { - require := require.New(t) - ctx := context.TODO() - ts := New(10, 10) - // Adds key/value - ts.SetScope(ctx, [][]byte{TestKey}) - ts.SetStorage(ctx, TestKey, TestVal) - // ChangedKey = true - difVal := []byte("difVal") - err := ts.Insert(ctx, TestKey, difVal) - require.NoError(err, "Error during insert.") - - require.Equal(2, ts.OpIndex(), "Operation not added.") - // Otherval should not be saved - ts.SetStorage(ctx, TestKey, []byte("OtherValue")) - require.Equal(difVal, ts.storage[string(TestKey)].v, "Value was not saved correctly.") -} - func TestGetValue(t *testing.T) { require := require.New(t) ctx := context.TODO() - ts := New(10, 10) - // Adds key/value - ts.SetStorage(ctx, TestKey, TestVal) - require.Equal(1, ts.OpIndex(), "Operation not added.") - require.Equal(TestVal, ts.storage[string(TestKey)].v, "Value was not saved correctly.") - _, err := ts.GetValue(ctx, TestKey) + ts := New(10) // GetValue without Scope perm + _, err := ts.GetValue(ctx, TestKey) require.ErrorIs(err, ErrKeyNotSpecified, "No error thrown.") // SetScope - ts.SetScope(ctx, [][]byte{TestKey}) + ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{string(TestKey): TestVal}) val, err := ts.GetValue(ctx, TestKey) require.NoError(err, "Error getting value.") require.Equal(TestVal, val, "Value was not saved correctly.") @@ -113,9 +62,9 @@ func TestGetValue(t *testing.T) { func TestGetValueNoStorage(t *testing.T) { require := require.New(t) ctx := context.TODO() - ts := New(10, 10) + ts := New(10) // SetScope but dont add to storage - ts.SetScope(ctx, [][]byte{TestKey}) + ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{}) _, err := ts.GetValue(ctx, TestKey) require.ErrorIs(database.ErrNotFound, err, "No error thrown.") } @@ -123,12 +72,12 @@ func TestGetValueNoStorage(t *testing.T) { func TestInsertNew(t *testing.T) { require := require.New(t) ctx := context.TODO() - ts := New(10, 10) + ts := New(10) // Insert before SetScope err := ts.Insert(ctx, TestKey, TestVal) require.ErrorIs(ErrKeyNotSpecified, err, "No error thrown.") // SetScope - ts.SetScope(ctx, [][]byte{TestKey}) + ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{}) // Insert key err = ts.Insert(ctx, TestKey, TestVal) require.NoError(err, "Error thrown.") @@ -141,25 +90,26 @@ func TestInsertNew(t *testing.T) { func TestInsertUpdate(t *testing.T) { require := require.New(t) ctx := context.TODO() - ts := New(10, 10) + ts := New(10) // SetScope and add - ts.SetScope(ctx, [][]byte{TestKey}) - ts.SetStorage(ctx, TestKey, TestVal) - require.Equal(1, ts.OpIndex(), "SetStorage operation was not added.") + ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{string(TestKey): TestVal}) + require.Equal(0, ts.OpIndex(), "SetStorage operation was not added.") // Insert key newVal := []byte("newVal") err := ts.Insert(ctx, TestKey, newVal) require.NoError(err, "Error thrown.") val, err := ts.GetValue(ctx, TestKey) require.NoError(err, "Error thrown.") - require.Equal(2, ts.OpIndex(), "Insert operation was not added.") + require.Equal(1, ts.OpIndex(), "Insert operation was not added.") require.Equal(newVal, val, "Value was not set correctly.") - require.Equal(TestVal, ts.ops[1].pastV.v, "PastVal was not set correctly.") + require.Equal(TestVal, ts.ops[0].pastV, "PastVal was not set correctly.") + require.False(ts.ops[0].pastChanged, "PastVal was not set correctly.") + require.True(ts.ops[0].pastExists, "PastVal was not set correctly.") } func TestFetchAndSetScope(t *testing.T) { require := require.New(t) - ts := New(10, 10) + ts := New(10) db := NewTestDB() ctx := context.TODO() keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} @@ -168,9 +118,9 @@ func TestFetchAndSetScope(t *testing.T) { err := db.Insert(ctx, key, vals[i]) require.NoError(err, "Error during insert.") } - err := ts.FetchAndSetScope(ctx, db, keys) + err := ts.FetchAndSetScope(ctx, keys, db) require.NoError(err, "Error thrown.") - require.Equal(len(keys), ts.OpIndex(), "Opertions not updated correctly.") + require.Equal(0, ts.OpIndex(), "Opertions not updated correctly.") require.Equal(keys, ts.scope, "Scope not updated correctly.") // Check values for i, key := range keys { @@ -182,7 +132,7 @@ func TestFetchAndSetScope(t *testing.T) { func TestFetchAndSetScopeMissingKey(t *testing.T) { require := require.New(t) - ts := New(10, 10) + ts := New(10) db := NewTestDB() ctx := context.TODO() keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} @@ -192,9 +142,9 @@ func TestFetchAndSetScopeMissingKey(t *testing.T) { err := db.Insert(ctx, key, vals[i]) require.NoError(err, "Error during insert.") } - err := ts.FetchAndSetScope(ctx, db, keys) + err := ts.FetchAndSetScope(ctx, keys, db) require.NoError(err, "Error thrown.") - require.Equal(len(keys)-1, ts.OpIndex(), "Opertions not updated correctly.") + require.Equal(0, ts.OpIndex(), "Opertions not updated correctly.") require.Equal(keys, ts.scope, "Scope not updated correctly.") // Check values for i, key := range keys[:len(keys)-1] { @@ -208,83 +158,66 @@ func TestFetchAndSetScopeMissingKey(t *testing.T) { func TestSetScope(t *testing.T) { require := require.New(t) - ts := New(10, 10) + ts := New(10) ctx := context.TODO() keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} - ts.SetScope(ctx, keys) + ts.SetScope(ctx, keys, map[string][]byte{}) require.Equal(keys, ts.scope, "Scope not updated correctly.") } -func TestRemove(t *testing.T) { +func TestRemoveInsertRollback(t *testing.T) { require := require.New(t) - ts := New(10, 10) + ts := New(10) ctx := context.TODO() - ts.SetScope(ctx, [][]byte{TestKey}) + ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{}) // Insert err := ts.Insert(ctx, TestKey, TestVal) require.NoError(err, "Error from insert.") + v, err := ts.GetValue(ctx, TestKey) + require.NoError(err) + require.Equal(TestVal, v) require.Equal(1, ts.OpIndex(), "Opertions not updated correctly.") // Remove err = ts.Remove(ctx, TestKey) require.NoError(err, "Error from remove.") - _, ok := ts.storage[string(TestKey)] - require.False(ok, "Key not deleted from storage.") + _, err = ts.GetValue(ctx, TestKey) + require.ErrorIs(err, database.ErrNotFound, "Key not deleted from storage.") require.Equal(2, ts.OpIndex(), "Opertions not updated correctly.") - require.Equal(TestVal, ts.ops[1].pastV.v, "Past value not set correctly.") - require.Equal(remove, ts.ops[1].action, "Action not set correctly.") - require.Equal(TestKey, ts.ops[1].k, "Action not set correctly.") + // Insert + err = ts.Insert(ctx, TestKey, TestVal) + require.NoError(err, "Error from insert.") + v, err = ts.GetValue(ctx, TestKey) + require.NoError(err) + require.Equal(TestVal, v) + require.Equal(3, ts.OpIndex(), "Opertions not updated correctly.") + require.Equal(1, ts.PendingChanges()) + // Rollback + ts.Rollback(ctx, 2) + _, err = ts.GetValue(ctx, TestKey) + require.ErrorIs(err, database.ErrNotFound, "Key not deleted from storage.") + // Rollback + ts.Rollback(ctx, 1) + v, err = ts.GetValue(ctx, TestKey) + require.NoError(err) + require.Equal(TestVal, v) } func TestRemoveNotInScope(t *testing.T) { require := require.New(t) - ts := New(10, 10) + ts := New(10) ctx := context.TODO() // Remove err := ts.Remove(ctx, TestKey) require.ErrorIs(err, ErrKeyNotSpecified, "ErrKeyNotSpecified should be thrown.") } -func TestRemoveNotInStorage(t *testing.T) { - require := require.New(t) - ts := New(10, 10) - ctx := context.TODO() - ts.SetScope(ctx, [][]byte{TestKey}) - // Remove a key that is not in storage - err := ts.Remove(ctx, TestKey) - require.NoError(err, "Error from remove.") -} - -func TestRestoreReads(t *testing.T) { - require := require.New(t) - ts := New(10, 10) - ctx := context.TODO() - keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} - vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - ts.SetScope(ctx, keys) - // Keys[3] not in db - for i, key := range keys { - ts.SetStorage(ctx, key, vals[i]) - } - require.Equal(len(keys), ts.OpIndex(), "Operations not added properly.") - // Roll back last two reads - ts.Rollback(ctx, 1) - require.Equal(1, ts.OpIndex(), "Operations not rolled back properly.") - for _, key := range keys[1:] { - _, ok := ts.storage[string(key)] - require.False(ok, "TState read op not rolled back properly.") - } - _, ok := ts.storage[string(keys[0])] - require.True(ok, "Rollbacked too far.") -} - func TestRestoreInsert(t *testing.T) { require := require.New(t) - ts := New(10, 10) + ts := New(10) ctx := context.TODO() keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - ts.SetScope(ctx, keys) - // Keys[3] not in db + ts.SetScope(ctx, keys, map[string][]byte{}) for i, key := range keys { err := ts.Insert(ctx, key, vals[i]) require.NoError(err, "Error inserting.") @@ -293,7 +226,6 @@ func TestRestoreInsert(t *testing.T) { err := ts.Insert(ctx, keys[0], updatedVal) require.NoError(err, "Error inserting.") require.Equal(len(keys)+1, ts.OpIndex(), "Operations not added properly.") - val, err := ts.GetValue(ctx, keys[0]) require.NoError(err, "Error getting value.") require.Equal(updatedVal, val, "Value not updated correctly.") @@ -301,25 +233,27 @@ func TestRestoreInsert(t *testing.T) { ts.Rollback(ctx, 2) require.Equal(2, ts.OpIndex(), "Operations not rolled back properly.") // Keys[2] was removed - _, ok := ts.storage[string(keys[2])] - require.False(ok, "TState read op not rolled back properly.") + _, err = ts.GetValue(ctx, keys[2]) + require.ErrorIs(err, database.ErrNotFound, "TState read op not rolled back properly.") // Keys[0] was set to past value val, err = ts.GetValue(ctx, keys[0]) - require.False(ok, "TState read op not rolled back properly.") require.NoError(err, "Error getting value.") require.Equal(vals[0], val, "Value not rolled back properly.") } func TestRestoreDelete(t *testing.T) { require := require.New(t) - ts := New(10, 10) + ts := New(10) ctx := context.TODO() keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - ts.SetScope(ctx, keys) - // Add + ts.SetScope(ctx, keys, map[string][]byte{ + string(keys[0]): vals[0], + string(keys[1]): vals[1], + string(keys[2]): vals[2], + }) + // Check scope for i, key := range keys { - ts.SetStorage(ctx, key, vals[i]) val, err := ts.GetValue(ctx, key) require.NoError(err, "Error getting value.") require.Equal(vals[i], val, "Value not set correctly.") @@ -328,14 +262,15 @@ func TestRestoreDelete(t *testing.T) { for _, key := range keys { err := ts.Remove(ctx, key) require.NoError(err, "Error removing from ts.") - _, err = ts.GetValue(ctx, key) require.ErrorIs(err, database.ErrNotFound, "Value not removed.") } - require.Equal(len(keys)*2, ts.OpIndex(), "Operations not added properly.") + require.Equal(len(keys), ts.OpIndex(), "Operations not added properly.") + require.Equal(3, ts.PendingChanges()) // Roll back all removes - ts.Rollback(ctx, 3) - require.Equal(3, ts.OpIndex(), "Operations not rolled back properly.") + ts.Rollback(ctx, 0) + require.Equal(0, ts.OpIndex(), "Operations not rolled back properly.") + require.Equal(0, ts.PendingChanges()) for i, key := range keys { val, err := ts.GetValue(ctx, key) require.NoError(err, "Error getting value.") @@ -345,14 +280,13 @@ func TestRestoreDelete(t *testing.T) { func TestWriteChanges(t *testing.T) { require := require.New(t) - ts := New(10, 10) + ts := New(10) db := NewTestDB() ctx := context.TODO() tracer, _ := trace.New(&trace.Config{Enabled: false}) keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - ts.SetScope(ctx, keys) - + ts.SetScope(ctx, keys, map[string][]byte{}) // Add for i, key := range keys { err := ts.Insert(ctx, key, vals[i]) @@ -363,21 +297,24 @@ func TestWriteChanges(t *testing.T) { } err := ts.WriteChanges(ctx, db, tracer) require.NoError(err, "Error writing changes.") - // Check if db was updated correctly for i, key := range keys { val, _ := db.GetValue(ctx, key) require.Equal(vals[i], val, "Value not updated in db.") } // Remove + ts = New(10) + ts.SetScope(ctx, keys, map[string][]byte{ + string(keys[0]): vals[0], + string(keys[1]): vals[1], + string(keys[2]): vals[2], + }) for _, key := range keys { err := ts.Remove(ctx, key) require.NoError(err, "Error removing from ts.") - _, err = ts.GetValue(ctx, key) require.ErrorIs(err, database.ErrNotFound, "Key not removed.") } - err = ts.WriteChanges(ctx, db, tracer) require.NoError(err, "Error writing changes.") // Check if db was updated correctly diff --git a/utils/utils.go b/utils/utils.go index e649c96869..ef83b5b8e0 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -13,11 +13,8 @@ import ( "strconv" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils/hashing" - "github.com/ava-labs/avalanchego/utils/json" "github.com/ava-labs/avalanchego/utils/perms" - "github.com/gorilla/rpc/v2" formatter "github.com/onsi/ginkgo/v2/formatter" ) @@ -32,32 +29,6 @@ func InitSubDirectory(rootPath string, name string) (string, error) { return p, os.MkdirAll(p, perms.ReadWriteExecute) } -// NewHandler returns a new Handler for a service where: -// - The handler's functionality is defined by [service] -// [service] should be a gorilla RPC service (see https://www.gorillatoolkit.org/pkg/rpc/v2) -// - The name of the service is [name] -// - The LockOption is the first element of [lockOption] -// By default the LockOption is WriteLock -// [lockOption] should have either 0 or 1 elements. Elements beside the first are ignored. -func NewHandler( - name string, - service interface{}, - lockOption ...common.LockOption, -) (*common.HTTPHandler, error) { - server := rpc.NewServer() - server.RegisterCodec(json.NewCodec(), "application/json") - server.RegisterCodec(json.NewCodec(), "application/json;charset=UTF-8") - if err := server.RegisterService(service, name); err != nil { - return nil, err - } - - var lock common.LockOption = common.NoLock - if len(lockOption) != 0 { - lock = lockOption[0] - } - return &common.HTTPHandler{LockOptions: lock, Handler: server}, nil -} - func ErrBytes(err error) []byte { return []byte(err.Error()) } diff --git a/vm/dependencies.go b/vm/dependencies.go index 662e18eca0..376a9c9c81 100644 --- a/vm/dependencies.go +++ b/vm/dependencies.go @@ -29,7 +29,6 @@ type Config interface { GetMempoolPayerSize() int GetMempoolExemptPayers() [][]byte GetMempoolVerifyBalances() bool - GetStreamingPort() uint16 GetStreamingBacklogSize() int GetStateHistoryLength() int // how many roots back of data to keep to serve state queries GetStateCacheSize() int // how many items to keep in value cache and node cache diff --git a/vm/errors.go b/vm/errors.go index 689c3c948d..9e21c9a4b8 100644 --- a/vm/errors.go +++ b/vm/errors.go @@ -8,10 +8,9 @@ import ( ) var ( - ErrNotAdded = errors.New("not added") - ErrDropped = errors.New("dropped") - ErrNotReady = errors.New("not ready") - ErrStateMissing = errors.New("state missing") - ErrMessageMissing = errors.New("message missing") - ErrStateSyncing = errors.New("state still syncing") + ErrNotAdded = errors.New("not added") + ErrDropped = errors.New("dropped") + ErrNotReady = errors.New("not ready") + ErrStateMissing = errors.New("state missing") + ErrStateSyncing = errors.New("state still syncing") ) diff --git a/vm/metrics.go b/vm/metrics.go index a8e02abad2..bd93522701 100644 --- a/vm/metrics.go +++ b/vm/metrics.go @@ -10,15 +10,15 @@ import ( ) type Metrics struct { - unitsVerified prometheus.Counter - unitsAccepted prometheus.Counter - txsSubmitted prometheus.Counter // includes gossip - txsVerified prometheus.Counter - txsAccepted prometheus.Counter - decisionsRPCConnections prometheus.Gauge - blocksRPCConnections prometheus.Gauge - rootCalculated metric.Averager - waitSignatures metric.Averager + unitsVerified prometheus.Counter + unitsAccepted prometheus.Counter + txsSubmitted prometheus.Counter // includes gossip + txsVerified prometheus.Counter + txsAccepted prometheus.Counter + stateChanges prometheus.Counter + stateOperations prometheus.Counter + rootCalculated metric.Averager + waitSignatures metric.Averager } func newMetrics() (*prometheus.Registry, *Metrics, error) { @@ -69,15 +69,15 @@ func newMetrics() (*prometheus.Registry, *Metrics, error) { Name: "txs_accepted", Help: "number of txs accepted by vm", }), - decisionsRPCConnections: prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: "vm", - Name: "decisions_rpc_connections", - Help: "number of open decisions connections", + stateChanges: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: "chain", + Name: "state_changes", + Help: "number of state changes", }), - blocksRPCConnections: prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: "vm", - Name: "blocks_rpc_connections", - Help: "number of open blocks connections", + stateOperations: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: "chain", + Name: "state_operations", + Help: "number of state operations", }), rootCalculated: rootCalculated, waitSignatures: waitSignatures, @@ -89,8 +89,8 @@ func newMetrics() (*prometheus.Registry, *Metrics, error) { r.Register(m.txsSubmitted), r.Register(m.txsVerified), r.Register(m.txsAccepted), - r.Register(m.decisionsRPCConnections), - r.Register(m.blocksRPCConnections), + r.Register(m.stateChanges), + r.Register(m.stateOperations), ) return r, m, errs.Err } diff --git a/vm/resolutions.go b/vm/resolutions.go index d1e46c1387..965fa74972 100644 --- a/vm/resolutions.go +++ b/vm/resolutions.go @@ -42,6 +42,14 @@ func (vm *VM) ChainID() ids.ID { return vm.snowCtx.ChainID } +func (vm *VM) NetworkID() uint32 { + return vm.snowCtx.NetworkID +} + +func (vm *VM) SubnetID() ids.ID { + return vm.snowCtx.SubnetID +} + func (vm *VM) ValidatorState() validators.State { return vm.snowCtx.ValidatorState } @@ -182,11 +190,15 @@ func (vm *VM) processAcceptedBlocks() { vm.warpManager.GatherSignatures(context.TODO(), tx.ID(), result.WarpMessage.Bytes()) } - // Update listeners - vm.listeners.AcceptBlock(b) + // Update server + if err := vm.webSocketServer.AcceptBlock(b); err != nil { + vm.snowCtx.Log.Fatal("unable to accept block in websocket server", zap.Error(err)) + } // Must clear accepted txs before [SetMinTx] or else we will errnoueously // send [ErrExpired] messages. - vm.listeners.SetMinTx(b.Tmstmp) + if err := vm.webSocketServer.SetMinTx(b.Tmstmp); err != nil { + vm.snowCtx.Log.Fatal("unable to set min tx in websocket server", zap.Error(err)) + } vm.snowCtx.Log.Info( "block processed", zap.Stringer("blkID", b.ID()), @@ -266,6 +278,16 @@ func (vm *VM) Proposers(ctx context.Context, diff int, depth int) (set.Set[ids.N return vm.proposerMonitor.Proposers(ctx, diff, depth) } +func (vm *VM) CurrentValidators( + ctx context.Context, +) (map[ids.NodeID]*validators.GetValidatorOutput, map[string]struct{}) { + return vm.proposerMonitor.Validators(ctx) +} + +func (vm *VM) GatherSignatures(ctx context.Context, txID ids.ID, msg []byte) { + vm.warpManager.GatherSignatures(ctx, txID, msg) +} + func (vm *VM) NodeID() ids.NodeID { return vm.snowCtx.NodeID } @@ -329,3 +351,11 @@ func (vm *VM) RecordRootCalculated(t time.Duration) { func (vm *VM) RecordWaitSignatures(t time.Duration) { vm.metrics.waitSignatures.Observe(float64(t)) } + +func (vm *VM) RecordStateChanges(c int) { + vm.metrics.stateChanges.Add(float64(c)) +} + +func (vm *VM) RecordStateOperations(c int) { + vm.metrics.stateOperations.Add(float64(c)) +} diff --git a/vm/storage.go b/vm/storage.go index 634172ba6d..af1e09b614 100644 --- a/vm/storage.go +++ b/vm/storage.go @@ -31,7 +31,7 @@ var ( lastAccepted = []byte("last_accepted") isSyncing = []byte("is_syncing") - signatureLRU = &cache.LRU[string, *WarpSignature]{Size: 1024} + signatureLRU = &cache.LRU[string, *chain.WarpSignature]{Size: 1024} ) func PrefixBlockIDKey(id ids.ID) []byte { @@ -144,17 +144,12 @@ func (vm *VM) StoreWarpSignature(txID ids.ID, signer *bls.PublicKey, signature [ k := PrefixWarpSignatureKey(txID, signer) // Cache any signature we produce for later queries from peers if bytes.Equal(vm.pkBytes, bls.PublicKeyToBytes(signer)) { - signatureLRU.Put(string(k), &WarpSignature{vm.pkBytes, signature}) + signatureLRU.Put(string(k), chain.NewWarpSignature(vm.pkBytes, signature)) } return vm.vmDB.Put(k, signature) } -type WarpSignature struct { - PublicKey []byte `json:"publicKey"` - Signature []byte `json:"signature"` -} - -func (vm *VM) GetWarpSignature(txID ids.ID, signer *bls.PublicKey) (*WarpSignature, error) { +func (vm *VM) GetWarpSignature(txID ids.ID, signer *bls.PublicKey) (*chain.WarpSignature, error) { k := PrefixWarpSignatureKey(txID, signer) if ws, ok := signatureLRU.Get(string(k)); ok { return ws, nil @@ -166,14 +161,14 @@ func (vm *VM) GetWarpSignature(txID ids.ID, signer *bls.PublicKey) (*WarpSignatu if err != nil { return nil, err } - ws := &WarpSignature{ + ws := &chain.WarpSignature{ PublicKey: bls.PublicKeyToBytes(signer), Signature: v, } return ws, nil } -func (vm *VM) GetWarpSignatures(txID ids.ID) ([]*WarpSignature, error) { +func (vm *VM) GetWarpSignatures(txID ids.ID) ([]*chain.WarpSignature, error) { prefix := make([]byte, 1+consts.IDLen) prefix[0] = warpSignaturePrefix copy(prefix[1:], txID[:]) @@ -181,10 +176,10 @@ func (vm *VM) GetWarpSignatures(txID ids.ID) ([]*WarpSignature, error) { defer iter.Release() // Collect all signatures we have for a txID - signatures := []*WarpSignature{} + signatures := []*chain.WarpSignature{} for iter.Next() { k := iter.Key() - signatures = append(signatures, &WarpSignature{ + signatures = append(signatures, &chain.WarpSignature{ PublicKey: k[len(k)-bls.PublicKeyLen:], Signature: iter.Value(), }) diff --git a/vm/streaming.go b/vm/streaming.go deleted file mode 100644 index cd5382dff9..0000000000 --- a/vm/streaming.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package vm - -import ( - "context" - "net/url" - "sync" - - "go.uber.org/zap" - - "github.com/ava-labs/avalanchego/ids" - "github.com/gorilla/websocket" - - "github.com/ava-labs/hypersdk/chain" - "github.com/ava-labs/hypersdk/codec" - "github.com/ava-labs/hypersdk/listeners" - "github.com/ava-labs/hypersdk/pubsub" -) - -func (vm *VM) StreamingPort() uint16 { - return vm.config.GetStreamingPort() -} - -// If you don't keep up, you will data -type Client struct { - conn *websocket.Conn - wl sync.Mutex - dll sync.Mutex - bll sync.Mutex - cl sync.Once -} - -// NewDecisionRPCClient creates a new client for the decision rpc server. -// Dials into the server at [uri] and returns a client. -func NewStreamingClient(uri string) (*Client, error) { - // nil for now until we want to pass in headers - u := url.URL{Scheme: "ws", Host: uri} - conn, resp, err := websocket.DefaultDialer.Dial(u.String(), nil) - // not using resp for now - resp.Body.Close() - if err != nil { - return nil, err - } - return &Client{conn: conn}, nil -} - -// IssueTx sends [tx] to the streaming rpc server. -func (c *Client) IssueTx(tx *chain.Transaction) error { - c.wl.Lock() - defer c.wl.Unlock() - - return c.conn.WriteMessage(websocket.BinaryMessage, tx.Bytes()) -} - -// ListenForTx listens for responses from the streamingServer. -func (c *Client) ListenForTx() (ids.ID, error, *chain.Result, error) { - c.dll.Lock() - defer c.dll.Unlock() - for { - _, msg, err := c.conn.ReadMessage() - if err != nil { - return ids.Empty, nil, nil, err - } - if msg == nil || msg[0] == listeners.DecisionMode { - return listeners.UnpackTxMessage(msg) - } - } -} - -// Close closes [c]'s connection to the decision rpc server. -func (c *Client) Close() error { - var err error - c.cl.Do(func() { - err = c.conn.Close() - }) - return err -} - -// streamingServerCallback is a callback function for the decision server. -// The server submits the tx to the vm and adds the tx to the vms listener for -// later retrieval. -func (vm *VM) streamingServerCallback(msgBytes []byte, c *pubsub.Connection) { - ctx, span := vm.tracer.Start(context.Background(), "decisionRPCServer callback") - defer span.End() - // Unmarshal TX - p := codec.NewReader(msgBytes, chain.NetworkSizeLimit) // will likely be much smaller - tx, err := chain.UnmarshalTx(p, vm.actionRegistry, vm.authRegistry) - if err != nil { - vm.snowCtx.Log.Error("failed to unmarshal tx", - zap.Int("len", len(msgBytes)), - zap.Error(err), - ) - return - } - // Verify tx - sigVerify := tx.AuthAsyncVerify() - if err := sigVerify(); err != nil { - vm.snowCtx.Log.Error("failed to verify sig", - zap.Error(err), - ) - return - } - // TODO: add tx associated with this connection - vm.listeners.AddTxListener(tx, c) - - // Submit will remove from [txWaiters] if it is not added - txID := tx.ID() - if err := vm.Submit(ctx, false, []*chain.Transaction{tx})[0]; err != nil { - vm.snowCtx.Log.Error("failed to submit tx", - zap.Stringer("txID", txID), - zap.Error(err), - ) - return - } - vm.snowCtx.Log.Debug("submitted tx", zap.Stringer("id", txID)) -} - -// Listen listens for block messages from the streaming server. -func (c *Client) ListenForBlock( - parser chain.Parser, -) (*chain.StatefulBlock, []*chain.Result, error) { - c.bll.Lock() - defer c.bll.Unlock() - for { - _, msg, err := c.conn.ReadMessage() - if err != nil { - return nil, nil, err - } - if msg == nil || msg[0] == listeners.BlockMode { - return listeners.UnpackBlockMessageBytes(msg, parser) - } - } -} diff --git a/vm/vm.go b/vm/vm.go index f12e6b8b61..1234d277fc 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -35,9 +35,9 @@ import ( "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/emap" "github.com/ava-labs/hypersdk/gossiper" - "github.com/ava-labs/hypersdk/listeners" "github.com/ava-labs/hypersdk/mempool" "github.com/ava-labs/hypersdk/pubsub" + "github.com/ava-labs/hypersdk/rpc" htrace "github.com/ava-labs/hypersdk/trace" hutils "github.com/ava-labs/hypersdk/utils" "github.com/ava-labs/hypersdk/workers" @@ -89,7 +89,7 @@ type VM struct { acceptorDone chan struct{} // Transactions that streaming users are currently subscribed to - listeners *listeners.Listeners + webSocketServer *rpc.WebSocketServer // Reuse gorotuine group to avoid constant re-allocation workers *workers.Workers @@ -99,8 +99,6 @@ type VM struct { lastAccepted *chain.StatelessBlock toEngine chan<- common.Message - streamingServer *pubsub.Server - // State Sync client and AppRequest handlers stateSyncClient *stateSyncerClient stateSyncNetworkClient syncEng.NetworkClient @@ -176,6 +174,7 @@ func (vm *VM) Initialize( if err != nil { return fmt.Errorf("implementation initialization failed: %w", err) } + // Setup tracer vm.tracer, err = htrace.New(vm.config.GetTraceConfig()) if err != nil { @@ -290,27 +289,6 @@ func (vm *VM) Initialize( vm.preferred, vm.lastAccepted = gBlkID, genesisBlk snowCtx.Log.Info("initialized vm from genesis", zap.Stringer("block", gBlkID)) } - - // Startup RPCs - serverAddr := fmt.Sprintf(":%d", vm.config.GetStreamingPort()) - vm.streamingServer = pubsub.New( - serverAddr, - vm.streamingServerCallback, - vm.Logger(), - pubsub.NewDefaultServerConfig(), - ) - go func() { - // Wait for VM to be ready before accepting connections. If we stop the VM - // before this happens, we should return. - if !vm.waitReady() { - return - } - err := vm.streamingServer.Start() - vm.snowCtx.Log.Error("Error starting decisions server", zap.Error(err)) - }() - // Setup listeners to the streamingServer - vm.listeners = listeners.New(vm.streamingServer) - go vm.processAcceptedBlocks() // Setup state syncing @@ -333,6 +311,25 @@ func (vm *VM) Initialize( // Wait until VM is ready and then send a state sync message to engine go vm.markReady() + + // Setup handlers + jsonRPCHandler, err := rpc.NewJSONRPCHandler(rpc.Name, rpc.NewJSONRPCServer(vm), common.NoLock) + if err != nil { + return fmt.Errorf("unable to create handler: %w", err) + } + if _, ok := vm.handlers[rpc.JSONRPCEndpoint]; ok { + return fmt.Errorf("duplicate JSONRPC handler found: %s", rpc.JSONRPCEndpoint) + } + vm.handlers[rpc.JSONRPCEndpoint] = jsonRPCHandler + if _, ok := vm.handlers[rpc.WebSocketEndpoint]; ok { + return fmt.Errorf("duplicate WebSocket handler found: %s", rpc.WebSocketEndpoint) + } + wcfg := pubsub.NewDefaultServerConfig() + wcfg.MaxPendingMessages = vm.config.GetStreamingBacklogSize() + vm.webSocketServer = rpc.NewWebSocketServer() + pubsubServer := pubsub.New(vm.snowCtx.Log, wcfg, vm.webSocketServer.MessageCallback(vm)) + vm.webSocketServer.SetBackend(pubsubServer) + vm.handlers[rpc.WebSocketEndpoint] = rpc.NewWebSocketHandler(pubsubServer) return nil } @@ -370,16 +367,6 @@ func (vm *VM) isReady() bool { } } -func (vm *VM) waitReady() bool { - select { - case <-vm.ready: - vm.snowCtx.Log.Info("wait ready returned") - return true - case <-vm.stop: - return false - } -} - func (vm *VM) Manager() manager.Manager { return vm.manager } @@ -466,12 +453,6 @@ func (vm *VM) Shutdown(ctx context.Context) error { close(vm.acceptedQueue) <-vm.acceptorDone - // Shutdown RPCs - // TODO: change to correct context - if err := vm.streamingServer.Shutdown(context.TODO()); err != nil { - return err - } - // Shutdown other async VM mechanisms vm.warpManager.Done() vm.builder.Done() @@ -659,7 +640,9 @@ func (vm *VM) submitStateless( // Failed signature verification is the only safe place to remove // a transaction in listeners. Every other case may still end up with // the transaction in a block. - vm.listeners.RemoveTx(txID, err) + if err := vm.webSocketServer.RemoveTx(txID, err); err != nil { + vm.snowCtx.Log.Warn("unable to remove tx from webSocketServer", zap.Error(err)) + } errs = append(errs, err) continue } @@ -717,7 +700,9 @@ func (vm *VM) Submit( // Failed signature verification is the only safe place to remove // a transaction in listeners. Every other case may still end up with // the transaction in a block. - vm.listeners.RemoveTx(txID, err) + if err := vm.webSocketServer.RemoveTx(txID, err); err != nil { + vm.snowCtx.Log.Warn("unable to remove tx from webSocketServer", zap.Error(err)) + } errs = append(errs, err) continue } diff --git a/vm/warp_manager.go b/vm/warp_manager.go index 8076f76f45..406501ed74 100644 --- a/vm/warp_manager.go +++ b/vm/warp_manager.go @@ -14,6 +14,7 @@ import ( "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/heap" @@ -249,7 +250,7 @@ func (w *WarpManager) AppRequest( w.vm.snowCtx.Log.Warn("could not store warp signature", zap.Error(err)) return nil } - sig = &WarpSignature{ + sig = &chain.WarpSignature{ PublicKey: w.vm.pkBytes, Signature: rSig, }