Skip to content

Proposervm API #4029

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f7bca02
proposervm epoch poc
cam-schultz Feb 25, 2025
46f9d06
verify p-chain epoch height
cam-schultz Mar 3, 2025
5e3941a
continuous epoch numbering scheme
cam-schultz May 6, 2025
d6873df
first epoch block sets start time
cam-schultz May 12, 2025
4e8239d
Merge branch 'master' into proposervm-epochs
cam-schultz May 12, 2025
5ec65ff
handle initial epoch
cam-schultz May 12, 2025
a17f763
sealing block's timestamp is next epoch's start time
cam-schultz May 13, 2025
8bcd2b7
Merge branch 'master' into proposervm-epochs
cam-schultz Jun 11, 2025
6e37eda
expose proposervm API
cam-schultz Jun 23, 2025
e250b85
lint
cam-schultz Jun 23, 2025
463b936
Merge branch 'master' into proposervm-api
cam-schultz Jun 23, 2025
79613f1
lock vm state
cam-schultz Jun 24, 2025
fa1802a
Merge branch 'proposervm-api' of github.com:ava-labs/avalanchego into…
cam-schultz Jun 24, 2025
0e093df
rename json tag
cam-schultz Jun 27, 2025
44dd186
Merge branch 'master' into proposervm-epochs
cam-schultz Jun 30, 2025
43e04c8
Merge branch 'master' into proposervm-api
cam-schultz Jul 3, 2025
0875cb7
proposervm api client
cam-schultz Jul 3, 2025
7ea6ee9
client fixes
cam-schultz Jul 3, 2025
3ecd42f
Merge branch 'proposervm-api' of github.com:ava-labs/avalanchego into…
cam-schultz Jul 3, 2025
dc586f4
specify epoch as duration
cam-schultz Jul 7, 2025
bd2da2c
update mocks
cam-schultz Jul 7, 2025
d1b1e5b
lint
cam-schultz Jul 7, 2025
d7bb8bf
add unit tests
ylg-avalabs Aug 7, 2025
1057ba5
Merge remote-tracking branch 'origin' into proposervm-api
ylg-avalabs Aug 7, 2025
bb9b56d
fix lints
ylg-avalabs Aug 8, 2025
07fdeb9
lints
ylg-avalabs Aug 8, 2025
cc79175
Merge remote-tracking branch 'origin/proposervm-epochs' into proposer…
ylg-avalabs Aug 12, 2025
19edd4d
return signedBlock
ylg-avalabs Aug 13, 2025
a560a1d
Merge remote-tracking branch 'origin' into proposervm-epochs
ylg-avalabs Aug 13, 2025
8c96927
fix lint
ylg-avalabs Aug 13, 2025
908ca7a
Merge branch 'proposervm-epochs' into proposervm-api
ylg-avalabs Aug 13, 2025
28c334b
Merge branch 'master' into proposervm-api
geoff-vball Aug 18, 2025
a2744e8
lint
geoff-vball Aug 18, 2025
66708ff
Merge branch 'master' into proposervm-api
geoff-vball Aug 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions vms/proposervm/block/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,16 @@ type SignedBlock interface {
}

type statelessUnsignedBlock struct {
ParentID ids.ID `serialize:"true"`
Timestamp int64 `serialize:"true"`
PChainHeight uint64 `serialize:"true"`
Certificate []byte `serialize:"true"`
Block []byte `serialize:"true"`
ParentID ids.ID `serialize:"true" json:"parentID"`
Timestamp int64 `serialize:"true" json:"timestamp"`
PChainHeight uint64 `serialize:"true" json:"pChainHeight"`
Certificate []byte `serialize:"true" json:"certificate"`
Block []byte `serialize:"true" json:"block"`
}

type statelessBlock struct {
StatelessBlock statelessUnsignedBlock `serialize:"true"`
Signature []byte `serialize:"true"`
StatelessBlock statelessUnsignedBlock `serialize:"true" json:"statelessBlock"`
Signature []byte `serialize:"true" json:"signature"`

id ids.ID
timestamp time.Time
Expand Down
52 changes: 52 additions & 0 deletions vms/proposervm/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package proposervm

import (
"context"
"fmt"

"github.com/ava-labs/avalanchego/api"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/formatting"
"github.com/ava-labs/avalanchego/utils/rpc"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add unit tests for this file?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added unit tests.


var _ Client = (*client)(nil)

type Client interface {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

who/what is consuming this interface? Usually in Golang, interfaces are declared where they're used, not where they're implemented.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Client interface will be used outside of the repo.

// GetProposedHeight returns the current height of this node's proposer VM.
GetProposedHeight(ctx context.Context, options ...rpc.Option) (uint64, error)
// GetProposerBlockWrapper returns the ProposerVM block wrapper
GetProposerBlockWrapper(ctx context.Context, proposerID ids.ID, options ...rpc.Option) ([]byte, error)
}

type client struct {
requester rpc.EndpointRequester
}

// NewClient returns a Client for interacting with the ProposerVM API.
// The provided blockchainName should be the blockchainID or an alias (e.g. "P" for the P-Chain).
func NewClient(uri string, blockchainName string) Client {
return &client{
requester: rpc.NewEndpointRequester(uri + fmt.Sprintf("/ext/bc/%s/proposervm", blockchainName)),
}
}

func (c *client) GetProposedHeight(ctx context.Context, options ...rpc.Option) (uint64, error) {
res := &api.GetHeightResponse{}
err := c.requester.SendRequest(ctx, "proposervm.getProposedHeight", struct{}{}, res, options...)
return uint64(res.Height), err
}

func (c *client) GetProposerBlockWrapper(ctx context.Context, proposerID ids.ID, options ...rpc.Option) ([]byte, error) {
res := &api.FormattedBlock{}
if err := c.requester.SendRequest(ctx, "proposervm.getProposerBlockWrapper", &GetProposerBlockArgs{
ProposerID: proposerID,
Encoding: formatting.Hex,
}, res, options...); err != nil {
return nil, err
}
return formatting.Decode(res.Encoding, res.Block)
}
70 changes: 70 additions & 0 deletions vms/proposervm/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add tests to the code in this file?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added unit tests

// See the file LICENSE for licensing terms.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lint job did not like including 2025 in the copyright date, nor did I find any instances of 2025 in the codebase, so I assume it is alright to add this file with a terminal date of 2024.


package proposervm

import (
"encoding/json"
"fmt"
"net/http"

"go.uber.org/zap"

"github.com/ava-labs/avalanchego/api"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/formatting"

avajson "github.com/ava-labs/avalanchego/utils/json"
)

type ProposerAPI struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for having the proposerVM as a field instead of just adding the functions to the proposerVM VM?

If we wish to isolate the VM object then it makes sense to add two functions or (an) interface(s) with:

  1. getLastAcceptedHeight
  2. GetBlock

and then it'll be easier to test.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the suggestion. Created the ProposerVMServer interface above and mocked it in the unit tests.

vm *VM
}

func (p *ProposerAPI) GetProposedHeight(_ *http.Request, _ *struct{}, reply *api.GetHeightResponse) error {
p.vm.ctx.Log.Debug("API called",
zap.String("service", "proposervm"),
zap.String("method", "getProposedHeight"),
)
p.vm.ctx.Lock.Lock()
defer p.vm.ctx.Lock.Unlock()

reply.Height = avajson.Uint64(p.vm.lastAcceptedHeight)
return nil
}

// GetProposerBlockArgs is the parameters supplied to the GetProposerBlockWrapper API
type GetProposerBlockArgs struct {
ProposerID ids.ID `json:"proposerID"`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this type rather than using the existing api.GetBlockArgs to eliminate confusion over what exactly this parameter is. It is the "Proposer ID" listed in the subnets explorer, e.g. https://subnets.avax.network/c-chain/block/64357262, not the block hash/ID as seen by the inner VM.

That said, I'm not sure what the best way to actually source the "Proposer ID" in the general case, since it's not exposed by the innerVM. Similarly, an endpoint to map Block hash -> Proposer ID seems like it would break encapsulation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word Proposer sounds like the actual proposer node. But if that's the common usage, I think it's fine. Maybe ProposerBlockID etc can make it cleaner. I think the actual word for that is OuterBlockID but sounds even more complicated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explorer uses Proposer ID/Proposer Node ID to disambiguate, but yes I agree the naming is awkward. Happy to go with whatever the consensus here is.

Encoding formatting.Encoding `json:"encoding"`
}

func (p *ProposerAPI) GetProposerBlockWrapper(r *http.Request, args *GetProposerBlockArgs, reply *api.GetBlockResponse) error {
p.vm.ctx.Log.Debug("API called",
zap.String("service", "proposervm"),
zap.String("method", "getProposerBlockWrapper"),
zap.String("proposerID", args.ProposerID.String()),
zap.String("encoding", args.Encoding.String()),
)
p.vm.ctx.Lock.Lock()
defer p.vm.ctx.Lock.Unlock()

block, err := p.vm.GetBlock(r.Context(), args.ProposerID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

am I reading this wrong and we're actually sending back the entire proposerVM block which also contains the inner VM block? If so, is it possible to only distill the proposerVM bytes?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proposerVM fields will be add in this PR, and we will only return these useful fields in the PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to rebase this PR on top of that PR (3746) ?

I am not a fan of leaving incomplete yet accessible APIs that are pending on other PRs.

Also, rebasing this PR on top of that PR would make it possible to properly change the output of the API in this PR and not need to wait for a new PR.

if err != nil {
return err
}
reply.Encoding = args.Encoding

var result any
if args.Encoding == formatting.JSON {
result = block
} else {
result, err = formatting.Encode(args.Encoding, block.Bytes())
if err != nil {
return fmt.Errorf("couldn't encode block %s as %s: %w", args.ProposerID, args.Encoding, err)
}
}

reply.Block, err = json.Marshal(result)
return err
}
138 changes: 138 additions & 0 deletions vms/proposervm/service.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
The ProposerVM API allows clients to fetch information about a Snowman++ chain's ProposerVM.

## Endpoint

```
/ext/bc/{blockchainID}/proposervm
```

## Format

This API uses the `json 2.0` RPC format.

## Methods

### `proposervm.getProposedHeight`

Returns this node's current proposer VM height.

**Signature:**

```
proposervm.getProposedHeight() ->
{
height: int,
}
```

**Example Call:**

```sh
curl -X POST --data '{
"jsonrpc": "2.0",
"method": "proposervm.getProposedHeight",
"params": {},
"id": 1
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/P/proposervm
```

**Example Response:**

```json
{
"jsonrpc": "2.0",
"result": {
"height": "56"
},
"id": 1
}
```

### `proposervm.getProposerBlockWrapper`

Get a block's ProposerVM wrapper by its proposer ID.

**Signature:**

```
proposervm.getProposerBlockWrapper({
proposerID: string
encoding: string // optional
}) -> {
block: string,
encoding: string
}
```

**Request:**

- `proposerID` is the proposer ID. It should be in cb58 format.
- `encoding` is the encoding format to use. Can be either `hex` or `json`. Defaults to `hex`.

#### Hex Example

**Example Call:**

```sh
curl -X POST --data '{
"jsonrpc": "2.0",
"method": "proposervm.getProposerBlockWrapper",
"params": {
"proposerID": "owJxcaDMaehbqoib8FRP7MuPdfGpSdXqD4hjkBtW4vcCJzr2Y",
"encoding": "hex"
},
"id": 1
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/C/proposervm
```

**Example Response:**

```json
{
"jsonrpc":"2.0",
"result":{
"block":"0x000000000000018a9f604b26237c49e54667b80e24433512774dd6bb8cd99032e927ae141dcd0000000068597a6d00000000000000000000053b308205373082031f020900baf3b5c5c6d0d14a300d06092a864886f70d01010b0500307f310b3009060355040613025553310b300906035504080c024e59310f300d06035504070c064974686163613110300e060355040a0c074176616c616273310e300c060355040b0c054765636b6f310c300a06035504030c036176613122302006092a864886f70d01090116137374657068656e406176616c6162732e6f72673020170d3139303730323136313231395a180f33303139303731303136313231395a303a310b3009060355040613025553310b300906035504080c024e593110300e060355040a0c074176616c616273310c300a06035504030c0361766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dd4e847ad276ba36e47d892014332ccf5c934c59541b2f24f9fe2642889dc107861630185fc6925626259770cfb39c382926ec8211e8790e9e9963715eee4e8786de85985a438f09e3a5099d904294834d06f8494f4e9fd4b26b0f2fe240b303ea0595a93014b776c5d036e9cc32d30696b7f94b497a5d7e32ee473f193c5882f79667d26d47f9b628d7abfb08e0047a89e2b3ea9c7077d2c0d83d983dd42cf5024f016f6c36235d3ccd056028ae9e22a34a5c927fe298b3499754e3ddb4c0cc580699df0c07219f17a2f54fefdd06d3ede2942367c6afe84e59321653cd3e55e381b7c6ba3f8b12f40421e7fe86f2ac2602a3b7610f5dc4df368aefc7dfe37e1866bf334af9ac45abcf022e161c0abf241ca4f4419b6c909c19e12fd1b990dea7e0e04cd0ab2067709894765283879670d801b7558216c41ceef7d415a267afc40fde917857fe01a24ef04cee4737403511811562e5f9dcd1a7aca55ef8583dfb130e8173d691436a76fa48f596d5dd9d12e7cebaa85dffbc166b8600ab29ce44fef31ec4145ef0a8b8c0aa8e355862f561947111ef1448a473eed5acf8f461a1bacd52ddfae38024df357ca38f628e17e2c66b3323d89773a31ad3217d5e0abc268b12bd5db8e925a8ee9c0af190f220252d5cb7549c96e462740f42b28808abe1535d0092a8d598221a2bc92d6ba54df8d489f08df68d75e408aa2718aac30203010001300d06092a864886f70d01010b050003820201009039dc03ede448c41bdb40751cfa6d1347311ad7b0427dc9356365b03774d95c319290f544d2eba303c48bdea8bcfc24cb191a88f54de045925ffad24f045fcfd36d4f126c9a1534b886dc5c0f14f1b99bf68c2a3df97854132bd15a2b539a8cb57acab79a4a2d5501de4e642fc02b1024085cb5503f851749c2630fb3507c43139423a06a48e0476bd0ac5c8cd5385bfc16a13dd4f640da7b37d2c29062bae76c7f7d6a97c5e568252d4a323f0c5fd9edc000287b6de11d938e62bef2b08b9264f27afce4a0c760f96a92bd1b77d12197cc6995a34174c3de46e7ea043973b6370b277c289a7aedf7981031e4a82c763750db084636e3e37cc89aeeeaf6ffc30fda69429bb6e9a44346604d80b640f463395e76d94458b75712f2a9ab06712cda1f0cf973bbce04e90e765a9c43321246f583c623751e400bfa1922402b8c72180e44080cc333e8a1af2b7d6b4b229397329c2e5466fff2b18d2c43e8be808af73e9cf9667ece4468e3edd342438c63ae9786deeb021972de57abf93ca0e9ae635d3f7658a0bf317058032d9940da32db4e2434a7af707ed1d9c4a4c484ddde7e4469c372e4c02d08d224ec9c1e2c287dacbacb501b7e8b89f8e2c3fe2d585f5877a69e003c0a81789fa790d29a748b16537a7d23e2e3741b635ff89786325aa839b2d674839d1ffa5b0e9a571a865a28a0ddefe6c0ef81657b9521efed5edc00000373f90370f90298a057b43873f59d6e0ff493909e8373502eaa828f0eb4d7dbe999b855f9b895d88ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940100000000000000000000000000000000000000a0685dcd731e76c2e6a503df5759ab11b324f03c8f26090637ad4dbb26ad5522fba04efbaa764431eba396b9edba52f594a6660473432761931c98b49ef00467b87ea0d95b673818fa493deec414e01e610d97ee287c9421c8eff4102b1647c1a184e4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010c83e4e1c082a4108468597a6db8560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421843b9aca0080808080a00000000000000000000000000000000000000000000000000000000000000000f8d0f866108534630b8a00825208948db97c7cece249c2b98bdc0226cc4c2a57bf52fc0180820a95a07ef0c6d54c839648ea6cfe191c37efc5ba9d9115695e061870b601c1ce647e75a03e066459cb0853a8d67f7b777753515b2ce627553a4d9b51b06ad6bdf3102b8ef866118534630b8a00825208948db97c7cece249c2b98bdc0226cc4c2a57bf52fc0180820a96a03a1c18eea77d4f08a29e482442ca2f2beb5bc8568e2768470ebdb6ae05382424a06e205ef2e0273c481f2e28a60e504575dba0714608601880d87906a98de41bb3c0808000000200bfcc7483e2c539c2d925a1b4eed65e2cbfae39e284be82d77ee86c078d1a1d4f54d8c46c6695d6619a589137eabcd4a8e93c94394c4d83458b122598b9d69f49f0c9e61b746ad46d91710c3475c97595d17c3ef1aa8c7e846fd8a231bdb68687a208ee752cd64ecf86288bec610688708e34d54ecb15ea4178e1dab8a74109ae1514240a08cddae9dc8ec6db17cfeffc2d0814e908e57676700c6fa3a4bbe1c84e537fd7d2f28491bf9ce3c5e57308027c198a3547ad9a23d365359b0c709c260abf6a04cdc59bcb4a65006c66c9cc2becae49e9275a2f3517260762a7b4c646121583590a25de74a924a66a43d2b1a6f09adfb664e552fc0b1b260594df57795b334c125627e112606d784790f6916dcb610f1e10a424f0ef8244cde7d2f0631e025b28d54f8a24003f3792b420a1eeb62a8a21a27e9eb7e3278b7e14dfc784e736ce2fad220ea9c2d97e98b451fd3a212b6b863a779d55a80ead284e49e0556792663e38316e7a31ac6b1915e96a1dd50924c48ffbb7d46df363baa3c8184457411577aa793cb3a9adef73301ecec8f7561f60a6187f13ed34d2e1756a7959fd5b10a4f753d07dd90022de2a2ea401e2516cd917a2a48c030e2cc0b2dfaa5dec82af18f6772462a5a58a9d128772b45f782c8a1854564158ed75531cc02d2050170de95147a8eb2b8364fb83d74722bee68e1654cf314d629599e3795617ff75c569bd",
"encoding":"hex"
},
"id":1
}
```

#### JSON Example

**Example Call:**

```sh
curl -X POST --data '{
"jsonrpc": "2.0",
"method": "proposervm.getProposerBlockWrapper",
"params": {
"proposerID": "owJxcaDMaehbqoib8FRP7MuPdfGpSdXqD4hjkBtW4vcCJzr2Y",
"encoding": "json"
},
"id": 1
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/C/proposervm
```

**Example Response:**

```json
{
"jsonrpc":"2.0",
"result":{
"block":{
"SignedBlock":{
"block":{
"parentID":"gNms4aTjhR3vLDsTLhNtuWdv6S6xpA6vLjPB53uWSYRxxi3b",
"timestamp":1750694509,
"pChainHeight":0,
"certificate":"MIIFNzCCAx8CCQC687XFxtDRSjANBgkqhkiG9w0BAQsFADB/MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxDzANBgNVBAcMBkl0aGFjYTEQMA4GA1UECgwHQXZhbGFiczEOMAwGA1UECwwFR2Vja28xDDAKBgNVBAMMA2F2YTEiMCAGCSqGSIb3DQEJARYTc3RlcGhlbkBhdmFsYWJzLm9yZzAgFw0xOTA3MDIxNjEyMTlaGA8zMDE5MDcxMDE2MTIxOVowOjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMRAwDgYDVQQKDAdBdmFsYWJzMQwwCgYDVQQDDANhdmEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDdToR60na6NuR9iSAUMyzPXJNMWVQbLyT5/iZCiJ3BB4YWMBhfxpJWJiWXcM+znDgpJuyCEeh5Dp6ZY3Fe7k6Hht6FmFpDjwnjpQmdkEKUg00G+ElPTp/UsmsPL+JAswPqBZWpMBS3dsXQNunMMtMGlrf5S0l6XX4y7kc/GTxYgveWZ9JtR/m2KNer+wjgBHqJ4rPqnHB30sDYPZg91Cz1Ak8Bb2w2I108zQVgKK6eIqNKXJJ/4pizSZdU4920wMxYBpnfDAchnxei9U/v3QbT7eKUI2fGr+hOWTIWU80+VeOBt8a6P4sS9AQh5/6G8qwmAqO3YQ9dxN82iu/H3+N+GGa/M0r5rEWrzwIuFhwKvyQcpPRBm2yQnBnhL9G5kN6n4OBM0KsgZ3CYlHZSg4eWcNgBt1WCFsQc7vfUFaJnr8QP3pF4V/4Bok7wTO5HN0A1EYEVYuX53NGnrKVe+Fg9+xMOgXPWkUNqdvpI9ZbV3Z0S5866qF3/vBZrhgCrKc5E/vMexBRe8Ki4wKqONVhi9WGUcRHvFEikc+7VrPj0YaG6zVLd+uOAJN81fKOPYo4X4sZrMyPYl3OjGtMhfV4KvCaLEr1duOklqO6cCvGQ8iAlLVy3VJyW5GJ0D0KyiAir4VNdAJKo1ZgiGivJLWulTfjUifCN9o115AiqJxiqwwIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQCQOdwD7eRIxBvbQHUc+m0TRzEa17BCfck1Y2WwN3TZXDGSkPVE0uujA8SL3qi8/CTLGRqI9U3gRZJf+tJPBF/P021PEmyaFTS4htxcDxTxuZv2jCo9+XhUEyvRWitTmoy1esq3mkotVQHeTmQvwCsQJAhctVA/hRdJwmMPs1B8QxOUI6BqSOBHa9CsXIzVOFv8FqE91PZA2ns30sKQYrrnbH99apfF5WglLUoyPwxf2e3AACh7beEdk45ivvKwi5Jk8nr85KDHYPlqkr0bd9Ehl8xplaNBdMPeRufqBDlztjcLJ3womnrt95gQMeSoLHY3UNsIRjbj43zImu7q9v/DD9ppQpu26aRDRmBNgLZA9GM5XnbZRFi3VxLyqasGcSzaHwz5c7vOBOkOdlqcQzISRvWDxiN1HkAL+hkiQCuMchgORAgMwzPooa8rfWtLIpOXMpwuVGb/8rGNLEPovoCK9z6c+WZ+zkRo4+3TQkOMY66Xht7rAhly3ler+Tyg6a5jXT92WKC/MXBYAy2ZQNoy204kNKevcH7R2cSkxITd3n5EacNy5MAtCNIk7JweLCh9rLrLUBt+i4n44sP+LVhfWHemngA8CoF4n6eQ0pp0ixZTen0j4uN0G2Nf+JeGMlqoObLWdIOdH/pbDppXGoZaKKDd7+bA74Fle5Uh7+1e3A==",
"block":"+QNw+QKYoFe0OHP1nW4P9JOQnoNzUC6qgo8OtNfb6Zm4Vfm4ldiMoB3MTejex116q4W1Z7bM1BrTEkUblIp0E/ChQv1A1JNHlAEAAAAAAAAAAAAAAAAAAAAAAAAAoGhdzXMedsLmpQPfV1mrEbMk8DyPJgkGN61NuyatVSL7oE77qnZEMeujlrntulL1lKZmBHNDJ2GTHJi0nvAEZ7h+oNlbZzgY+kk97sQU4B5hDZfuKHyUIcjv9BArFkfBoYTkuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMg+ThwIKkEIRoWXptuFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAAAAAAAKBW6B8XG8xVpv+DReaSwPhuW0jgG5lsrcABYi+142O0IYQ7msoAgICAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPjQ+GYQhTRjC4oAglIIlI25fHzs4knCuYvcAibMTCpXv1L8AYCCCpWgfvDG1UyDlkjqbP4ZHDfvxbqdkRVpXgYYcLYBwc5kfnWgPgZkWcsIU6jWf3t3d1NRWyzmJ1U6TZtRsGrWvfMQK474ZhGFNGMLigCCUgiUjbl8fOziScK5i9wCJsxMKle/UvwBgIIKlqA6HBjup31PCKKeSCRCyi8r61vIVo4naEcOvbauBTgkJKBuIF7y4Cc8SB8uKKYOUEV126BxRghgGIDYeQapjeQbs8CAgA=="},
"signature":"v8x0g+LFOcLZJaG07tZeLL+uOeKEvoLXfuhsB40aHU9U2MRsZpXWYZpYkTfqvNSo6TyUOUxNg0WLEiWYudafSfDJ5ht0atRtkXEMNHXJdZXRfD7xqox+hG/YojG9toaHogjudSzWTs+GKIvsYQaIcI401U7LFepBeOHauKdBCa4VFCQKCM3a6dyOxtsXz+/8LQgU6QjldnZwDG+jpLvhyE5Tf9fS8oSRv5zjxeVzCAJ8GYo1R62aI9NlNZsMcJwmCr9qBM3Fm8tKZQBsZsnMK+yuSeknWi81FyYHYqe0xkYSFYNZCiXedKkkpmpD0rGm8JrftmTlUvwLGyYFlN9XeVszTBJWJ+ESYG14R5D2kW3LYQ8eEKQk8O+CRM3n0vBjHgJbKNVPiiQAPzeStCCh7rYqiiGifp634yeLfhTfx4TnNs4vrSIOqcLZfpi0Uf06IStrhjp3nVWoDq0oTkngVWeSZj44MW56MaxrGRXpah3VCSTEj/u31G3zY7qjyBhEV0EVd6p5PLOpre9zMB7OyPdWH2CmGH8T7TTS4XVqeVn9WxCk91PQfdkAIt4qLqQB4lFs2ReipIwDDizAst+qXeyCrxj2dyRipaWKnRKHcrRfeCyKGFRWQVjtdVMcwC0gUBcN6VFHqOsrg2T7g9dHIr7mjhZUzzFNYpWZ43lWF/8="
}
},
"encoding":"json"
},
"id":1
}
```
22 changes: 22 additions & 0 deletions vms/proposervm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
"context"
"errors"
"fmt"
"net/http"
"time"

"github.com/gorilla/rpc/v2"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap"

Expand All @@ -24,6 +26,7 @@ import (
"github.com/ava-labs/avalanchego/snow/engine/common"
"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/json"
"github.com/ava-labs/avalanchego/utils/math"
"github.com/ava-labs/avalanchego/utils/timer/mockable"
"github.com/ava-labs/avalanchego/utils/units"
Expand Down Expand Up @@ -251,6 +254,25 @@ func (vm *VM) Shutdown(ctx context.Context) error {
return vm.ChainVM.Shutdown(ctx)
}

// overriddes ChainVM.CreateHandlers to expose the proposervm API path
func (vm *VM) CreateHandlers(ctx context.Context) (map[string]http.Handler, error) {
handlers, err := vm.ChainVM.CreateHandlers(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create inner VM handlers: %w", err)
}

server := rpc.NewServer()
server.RegisterCodec(json.NewCodec(), "application/json")
server.RegisterCodec(json.NewCodec(), "application/json;charset=UTF-8")
err = server.RegisterService(&ProposerAPI{vm}, "proposervm")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we have these two also similar to the platformVM?

	server.RegisterInterceptFunc(vm.metrics.InterceptRequest)
	server.RegisterAfterFunc(vm.metrics.AfterRequest)

Any thoughts about this?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added metrics for the proposer vm. Currently, it only contains metrics.APIInterceptor. We will add more metrics when we need later.

if err != nil {
return nil, fmt.Errorf("failed to register proposervm service: %w", err)
}
handlers["/proposervm"] = server

return handlers, nil
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about a unit test that invokes this API?

func (vm *VM) SetState(ctx context.Context, newState snow.State) error {
if err := vm.ChainVM.SetState(ctx, newState); err != nil {
return err
Expand Down