Skip to content

Commit

Permalink
WIP: CCQ deserialize response
Browse files Browse the repository at this point in the history
  • Loading branch information
evan-gray committed May 25, 2023
1 parent 4ed601d commit 5ff26eb
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 67 deletions.
39 changes: 35 additions & 4 deletions node/hack/query/send_req.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package main

import (
"bytes"
"context"
"crypto/ecdsa"
"encoding/hex"
Expand All @@ -17,7 +18,9 @@ import (
"github.com/certusone/wormhole/node/pkg/p2p"
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
nodev1 "github.com/certusone/wormhole/node/pkg/proto/node/v1"
"github.com/ethereum/go-ethereum/accounts/abi"
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethCrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/libp2p/go-libp2p"
dht "github.com/libp2p/go-libp2p-kad-dht"
Expand Down Expand Up @@ -176,8 +179,19 @@ func main() {
// END SETUP
//

wethAbi, err := abi.JSON(strings.NewReader("[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]"))
if err != nil {
panic(err)
}

// methodName := "totalSupply"
methodName := "name"
data, err := wethAbi.Pack(methodName)
if err != nil {
panic(err)
}

to, _ := hex.DecodeString("0d500b1d8e8ef31e21c99d1db9a6444d3adf1270")
data, _ := hex.DecodeString("18160ddd")
// block := "0x28d9630"
block := "latest"
// block := "0x9999bac44d09a7f69ee7941819b0a19c59ccb1969640cc513be09ef95ed2d8e2"
Expand Down Expand Up @@ -227,6 +241,7 @@ func main() {

logger.Info("Waiting for message...")
// TODO: max wait time
// TODO: accumulate signatures to reach quorum
for {
envelope, err := sub.Next(ctx)
if err != nil {
Expand All @@ -243,9 +258,25 @@ func main() {
var isMatchingResponse bool
switch m := msg.Message.(type) {
case *gossipv1.GossipMessage_SignedQueryResponse:
// TODO: check if it's matching
logger.Info("response received", zap.Any("response", m.SignedQueryResponse))
isMatchingResponse = true
logger.Info("query response received", zap.Any("response", m.SignedQueryResponse))
response, err := common.UnmarshalQueryResponsePublication(m.SignedQueryResponse.QueryResponse)
if err != nil {
logger.Warn("failed to unmarshal response", zap.Error(err))
break
}
if bytes.Equal(response.Request.QueryRequest, queryRequestBytes) && bytes.Equal(response.Request.Signature, sig) {
// TODO: verify response signature
isMatchingResponse = true

result, err := wethAbi.Methods[methodName].Outputs.Unpack(response.Response.Result)
if err != nil {
logger.Warn("failed to unpack result", zap.Error(err))
break
}

resultStr := hexutil.Encode(response.Response.Result)
logger.Info("found matching response", zap.String("number", response.Response.Number.String()), zap.String("hash", response.Response.Hash.String()), zap.String("time", response.Response.Time.String()), zap.Any("resultDecoded", result), zap.String("resultStr", resultStr))
}
default:
continue
}
Expand Down
197 changes: 136 additions & 61 deletions node/pkg/common/queryResponse.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import (
"encoding/binary"
"fmt"
"math"
"math/big"
"time"

gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
"github.com/ethereum/go-ethereum/common"
eth_hexutil "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
"google.golang.org/protobuf/proto"
Expand All @@ -18,9 +18,9 @@ import (
var queryResponsePrefix = []byte("query_response_0000000000000000000|")

type EthCallQueryResponse struct {
Number *eth_hexutil.Big
Number *big.Int
Hash common.Hash
Time eth_hexutil.Uint64
Time time.Time
Result []byte
}

Expand All @@ -29,6 +29,7 @@ type QueryResponsePublication struct {
Response EthCallQueryResponse
}

// Marshal serializes the binary representation of a query response
func (msg *QueryResponsePublication) Marshal() ([]byte, error) {
// TODO: copy request write checks to query module request handling
// TODO: only receive the unmarshalled query request (see note in query.go)
Expand All @@ -51,8 +52,11 @@ func (msg *QueryResponsePublication) Marshal() ([]byte, error) {
switch req := queryRequest.Message.(type) {
case *gossipv1.QueryRequest_EthCallQueryRequest:
vaa.MustWrite(buf, binary.BigEndian, uint8(1))
vaa.MustWrite(buf, binary.BigEndian, queryRequest.ChainId) // uint32
vaa.MustWrite(buf, binary.BigEndian, queryRequest.Nonce) // uint32
if queryRequest.ChainId > math.MaxUint16 {
return nil, fmt.Errorf("invalid chain id: %d is out of bounds", queryRequest.ChainId)
}
vaa.MustWrite(buf, binary.BigEndian, uint16(queryRequest.ChainId))
vaa.MustWrite(buf, binary.BigEndian, queryRequest.Nonce) // uint32
if len(req.EthCallQueryRequest.To) != 20 {
return nil, fmt.Errorf("invalid length for To contract")
}
Expand All @@ -71,12 +75,13 @@ func (msg *QueryResponsePublication) Marshal() ([]byte, error) {

// Response
// TODO: probably some kind of request/response pair validation
vaa.MustWrite(buf, binary.BigEndian, msg.Response.Number.ToInt().Uint64())
// TODO: is uint64 safe?
vaa.MustWrite(buf, binary.BigEndian, msg.Response.Number.Uint64())
if len(msg.Response.Hash) != 32 {
return nil, fmt.Errorf("invalid length for block hash")
}
buf.Write(msg.Response.Hash[:])
vaa.MustWrite(buf, binary.BigEndian, uint32(time.Unix(int64(msg.Response.Time), 0).Unix()))
vaa.MustWrite(buf, binary.BigEndian, uint32(msg.Response.Time.Unix()))
if len(msg.Response.Result) > math.MaxUint32 {
return nil, fmt.Errorf("response data too long")
}
Expand All @@ -88,60 +93,130 @@ func (msg *QueryResponsePublication) Marshal() ([]byte, error) {
}
}

// TODO
// Unmarshal deserializes the binary representation of a VAA
// func UnmarshalMessagePublication(data []byte) (*MessagePublication, error) {
// if len(data) < minMsgLength {
// return nil, fmt.Errorf("message is too short")
// }

// msg := &MessagePublication{}

// reader := bytes.NewReader(data[:])

// txHash := common.Hash{}
// if n, err := reader.Read(txHash[:]); err != nil || n != 32 {
// return nil, fmt.Errorf("failed to read TxHash [%d]: %w", n, err)
// }
// msg.TxHash = txHash

// unixSeconds := uint32(0)
// if err := binary.Read(reader, binary.BigEndian, &unixSeconds); err != nil {
// return nil, fmt.Errorf("failed to read timestamp: %w", err)
// }
// msg.Timestamp = time.Unix(int64(unixSeconds), 0)

// if err := binary.Read(reader, binary.BigEndian, &msg.Nonce); err != nil {
// return nil, fmt.Errorf("failed to read nonce: %w", err)
// }

// if err := binary.Read(reader, binary.BigEndian, &msg.Sequence); err != nil {
// return nil, fmt.Errorf("failed to read sequence: %w", err)
// }

// if err := binary.Read(reader, binary.BigEndian, &msg.ConsistencyLevel); err != nil {
// return nil, fmt.Errorf("failed to read consistency level: %w", err)
// }

// if err := binary.Read(reader, binary.BigEndian, &msg.EmitterChain); err != nil {
// return nil, fmt.Errorf("failed to read emitter chain: %w", err)
// }

// emitterAddress := vaa.Address{}
// if n, err := reader.Read(emitterAddress[:]); err != nil || n != 32 {
// return nil, fmt.Errorf("failed to read emitter address [%d]: %w", n, err)
// }
// msg.EmitterAddress = emitterAddress

// payload := make([]byte, reader.Len())
// n, err := reader.Read(payload)
// if err != nil || n == 0 {
// return nil, fmt.Errorf("failed to read payload [%d]: %w", n, err)
// }
// msg.Payload = payload[:n]

// return msg, nil
// }
// Unmarshal deserializes the binary representation of a query response
func UnmarshalQueryResponsePublication(data []byte) (*QueryResponsePublication, error) {
// if len(data) < minMsgLength {
// return nil, fmt.Errorf("message is too short")
// }

msg := &QueryResponsePublication{}

reader := bytes.NewReader(data[:])

// Request
requestChain := vaa.ChainID(0)
if err := binary.Read(reader, binary.BigEndian, &requestChain); err != nil {
return nil, fmt.Errorf("failed to read request chain: %w", err)
}
if requestChain != vaa.ChainIDUnset {
// TODO: support reading off-chain and on-chain requests
return nil, fmt.Errorf("unsupported request chain: %d", requestChain)
}

signedQueryRequest := &gossipv1.SignedQueryRequest{}
signature := [65]byte{}
if n, err := reader.Read(signature[:]); err != nil || n != 65 {
return nil, fmt.Errorf("failed to read signature [%d]: %w", n, err)
}
signedQueryRequest.Signature = signature[:]

requestType := uint8(0)
if err := binary.Read(reader, binary.BigEndian, &requestType); err != nil {
return nil, fmt.Errorf("failed to read request chain: %w", err)
}
if requestType != 1 {
// TODO: support reading different types of request/response pairs
return nil, fmt.Errorf("unsupported request type: %d", requestType)
}

queryRequest := &gossipv1.QueryRequest{}
queryChain := vaa.ChainID(0)
if err := binary.Read(reader, binary.BigEndian, &queryChain); err != nil {
return nil, fmt.Errorf("failed to read request chain: %w", err)
}
queryRequest.ChainId = uint32(queryChain)

queryNonce := uint32(0)
if err := binary.Read(reader, binary.BigEndian, &queryNonce); err != nil {
return nil, fmt.Errorf("failed to read request nonce: %w", err)
}
queryRequest.Nonce = queryNonce

ethCallQueryRequest := &gossipv1.EthCallQueryRequest{}

queryEthCallTo := [20]byte{}
if n, err := reader.Read(queryEthCallTo[:]); err != nil || n != 20 {
return nil, fmt.Errorf("failed to read call To [%d]: %w", n, err)
}
ethCallQueryRequest.To = queryEthCallTo[:]

queryEthCallDataLen := uint32(0)
if err := binary.Read(reader, binary.BigEndian, &queryEthCallDataLen); err != nil {
return nil, fmt.Errorf("failed to read call Data len: %w", err)
}
queryEthCallData := make([]byte, queryEthCallDataLen)
if n, err := reader.Read(queryEthCallData[:]); err != nil || n != int(queryEthCallDataLen) {
return nil, fmt.Errorf("failed to read call To [%d]: %w", n, err)
}
ethCallQueryRequest.Data = queryEthCallData[:]

queryEthCallBlockLen := uint32(0)
if err := binary.Read(reader, binary.BigEndian, &queryEthCallBlockLen); err != nil {
return nil, fmt.Errorf("failed to read call Data len: %w", err)
}
queryEthCallBlockBytes := make([]byte, queryEthCallBlockLen)
if n, err := reader.Read(queryEthCallBlockBytes[:]); err != nil || n != int(queryEthCallBlockLen) {
return nil, fmt.Errorf("failed to read call To [%d]: %w", n, err)
}
ethCallQueryRequest.Block = string(queryEthCallBlockBytes[:])

queryRequest.Message = &gossipv1.QueryRequest_EthCallQueryRequest{
EthCallQueryRequest: ethCallQueryRequest,
}
queryRequestBytes, err := proto.Marshal(queryRequest)
if err != nil {
return nil, err
}
signedQueryRequest.QueryRequest = queryRequestBytes

msg.Request = signedQueryRequest

// Response
queryResponse := EthCallQueryResponse{}

responseNumber := uint64(0)
if err := binary.Read(reader, binary.BigEndian, &responseNumber); err != nil {
return nil, fmt.Errorf("failed to read response number: %w", err)
}
responseNumberBig := big.NewInt(0).SetUint64(responseNumber)
queryResponse.Number = responseNumberBig

responseHash := common.Hash{}
if n, err := reader.Read(responseHash[:]); err != nil || n != 32 {
return nil, fmt.Errorf("failed to read response hash [%d]: %w", n, err)
}
queryResponse.Hash = responseHash

unixSeconds := uint32(0)
if err := binary.Read(reader, binary.BigEndian, &unixSeconds); err != nil {
return nil, fmt.Errorf("failed to read response timestamp: %w", err)
}
queryResponse.Time = time.Unix(int64(unixSeconds), 0)

responseResultLen := uint32(0)
if err := binary.Read(reader, binary.BigEndian, &responseResultLen); err != nil {
return nil, fmt.Errorf("failed to read response len: %w", err)
}
responseResult := make([]byte, responseResultLen)
if n, err := reader.Read(responseResult[:]); err != nil || n != int(responseResultLen) {
return nil, fmt.Errorf("failed to read result [%d]: %w", n, err)
}
queryResponse.Result = responseResult[:]

msg.Response = queryResponse

return msg, nil
}

// Similar to sdk/vaa/structs.go,
// In order to save space in the solana signature verification instruction, we hash twice so we only need to pass in
Expand Down
4 changes: 2 additions & 2 deletions node/pkg/watchers/evm/watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,9 +677,9 @@ func (w *Watcher) Run(parentCtx context.Context) error {
queryResponse := common.QueryResponsePublication{
Request: signedQueryRequest,
Response: common.EthCallQueryResponse{
Number: blockResult.Number,
Number: blockResult.Number.ToInt(),
Hash: blockResult.Hash,
Time: blockResult.Time,
Time: time.Unix(int64(blockResult.Time), 0),
Result: callResult,
},
}
Expand Down

0 comments on commit 5ff26eb

Please sign in to comment.