Skip to content

Commit

Permalink
Universal CLI (#1662)
Browse files Browse the repository at this point in the history
Creates a single binary that can be used with any HyperSDK VM, as long as the VM uses standard functionality.

Signed-off-by: containerman17 <8990432+containerman17@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Aaron Buchwald <aaron.buchwald56@gmail.com>
  • Loading branch information
3 people authored Oct 25, 2024
1 parent ef66271 commit 960b790
Show file tree
Hide file tree
Showing 29 changed files with 1,393 additions and 90 deletions.
45 changes: 45 additions & 0 deletions abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,48 @@ func describeStruct(t reflect.Type) ([]Field, []reflect.Type, error) {

return fields, otherStructsSeen, nil
}

func (a *ABI) FindOutputByID(id uint8) (TypedStruct, bool) {
for _, output := range a.Outputs {
if output.ID == id {
return output, true
}
}
return TypedStruct{}, false
}

func (a *ABI) FindActionByID(id uint8) (TypedStruct, bool) {
for _, action := range a.Actions {
if action.ID == id {
return action, true
}
}
return TypedStruct{}, false
}

func (a *ABI) FindOutputByName(name string) (TypedStruct, bool) {
for _, output := range a.Outputs {
if output.Name == name {
return output, true
}
}
return TypedStruct{}, false
}

func (a *ABI) FindActionByName(name string) (TypedStruct, bool) {
for _, action := range a.Actions {
if action.Name == name {
return action, true
}
}
return TypedStruct{}, false
}

func (a *ABI) FindTypeByName(name string) (Type, bool) {
for _, typ := range a.Types {
if typ.Name == name {
return typ, true
}
}
return Type{}, false
}
60 changes: 45 additions & 15 deletions abi/dynamic/reflect_marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
var ErrTypeNotFound = errors.New("type not found in ABI")

func Marshal(inputABI abi.ABI, typeName string, jsonData string) ([]byte, error) {
if _, ok := findABIType(inputABI, typeName); !ok {
if _, ok := inputABI.FindTypeByName(typeName); !ok {
return nil, fmt.Errorf("marshalling %s: %w", typeName, ErrTypeNotFound)
}

Expand All @@ -41,19 +41,57 @@ func Marshal(inputABI abi.ABI, typeName string, jsonData string) ([]byte, error)
return nil, fmt.Errorf("failed to unmarshal JSON data: %w", err)
}

writer := codec.NewWriter(0, consts.NetworkSizeLimit)
var typeID byte
found := false
for _, action := range inputABI.Actions {
if action.Name == typeName {
typeID = action.ID
found = true
break
}
}
if !found {
return nil, fmt.Errorf("action %s not found in ABI", typeName)
}

writer := codec.NewWriter(1, consts.NetworkSizeLimit)
writer.PackByte(typeID)
if err := codec.LinearCodec.MarshalInto(value, writer.Packer); err != nil {
return nil, fmt.Errorf("failed to marshal struct: %w", err)
}

return writer.Bytes(), nil
}

func Unmarshal(inputABI abi.ABI, typeName string, data []byte) (string, error) {
if _, ok := findABIType(inputABI, typeName); !ok {
return "", fmt.Errorf("unmarshalling %s: %w", typeName, ErrTypeNotFound)
func UnmarshalOutput(inputABI abi.ABI, data []byte) (string, error) {
if len(data) == 0 {
return "", nil
}

typeID := data[0]
outputType, ok := inputABI.FindOutputByID(typeID)
if !ok {
return "", fmt.Errorf("output with id %d not found in ABI", typeID)
}

return Unmarshal(inputABI, data[1:], outputType.Name)
}

func UnmarshalAction(inputABI abi.ABI, data []byte) (string, error) {
if len(data) == 0 {
return "", nil
}

typeID := data[0]
actionType, ok := inputABI.FindActionByID(typeID)
if !ok {
return "", fmt.Errorf("action with id %d not found in ABI", typeID)
}

return Unmarshal(inputABI, data[1:], actionType.Name)
}

func Unmarshal(inputABI abi.ABI, data []byte, typeName string) (string, error) {
typeCache := make(map[string]reflect.Type)

typ, err := getReflectType(typeName, inputABI, typeCache)
Expand Down Expand Up @@ -134,13 +172,14 @@ func getReflectType(abiTypeName string, inputABI abi.ABI, typeCache map[string]r
return cachedType, nil
}

abiType, ok := findABIType(inputABI, abiTypeName)
abiType, ok := inputABI.FindTypeByName(abiTypeName)
if !ok {
return nil, fmt.Errorf("type %s not found in ABI", abiTypeName)
}

// It is a struct, as we don't support anything else as custom types
fields := make([]reflect.StructField, len(abiType.Fields))

for i, field := range abiType.Fields {
fieldType, err := getReflectType(field.Type, inputABI, typeCache)
if err != nil {
Expand All @@ -159,12 +198,3 @@ func getReflectType(abiTypeName string, inputABI abi.ABI, typeCache map[string]r
return structType, nil
}
}

func findABIType(inputABI abi.ABI, typeName string) (abi.Type, bool) {
for _, typ := range inputABI.Types {
if typ.Name == typeName {
return typ, true
}
}
return abi.Type{}, false
}
8 changes: 5 additions & 3 deletions abi/dynamic/reflect_marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ func TestDynamicMarshal(t *testing.T) {
require.NoError(err)

// Compare with expected hex
expectedHex := string(mustReadFile(t, "../testdata/"+tc.name+".hex"))
expectedHex = strings.TrimSpace(expectedHex)
expectedTypeID, found := abi.FindActionByName(tc.typeName)
require.True(found, "action %s not found in ABI", tc.typeName)

expectedHex := hex.EncodeToString([]byte{expectedTypeID.ID}) + strings.TrimSpace(string(mustReadFile(t, "../testdata/"+tc.name+".hex")))
require.Equal(expectedHex, hex.EncodeToString(objectBytes))

unmarshaledJSON, err := Unmarshal(abi, tc.typeName, objectBytes)
unmarshaledJSON, err := UnmarshalAction(abi, objectBytes)
require.NoError(err)

// Compare with expected JSON
Expand Down
16 changes: 10 additions & 6 deletions api/indexer/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ func (i *Indexer) storeTransactions(blk *chain.ExecutedBlock) error {
result.Units,
result.Fee,
result.Outputs,
string(result.Error),
); err != nil {
return err
}
Expand All @@ -190,6 +191,7 @@ func (*Indexer) storeTransaction(
units fees.Dimensions,
fee uint64,
outputs [][]byte,
errorStr string,
) error {
outputLength := consts.ByteLen // Single byte containing number of outputs
for _, output := range outputs {
Expand All @@ -206,19 +208,20 @@ func (*Indexer) storeTransaction(
for _, output := range outputs {
writer.PackBytes(output)
}
writer.PackString(errorStr)
if err := writer.Err(); err != nil {
return err
}
return batch.Put(txID[:], writer.Bytes())
}

func (i *Indexer) GetTransaction(txID ids.ID) (bool, int64, bool, fees.Dimensions, uint64, [][]byte, error) {
func (i *Indexer) GetTransaction(txID ids.ID) (bool, int64, bool, fees.Dimensions, uint64, [][]byte, string, error) {
v, err := i.txDB.Get(txID[:])
if errors.Is(err, database.ErrNotFound) {
return false, 0, false, fees.Dimensions{}, 0, nil, nil
return false, 0, false, fees.Dimensions{}, 0, nil, "", nil
}
if err != nil {
return false, 0, false, fees.Dimensions{}, 0, nil, err
return false, 0, false, fees.Dimensions{}, 0, nil, "", err
}
reader := codec.NewReader(v, consts.NetworkSizeLimit)
timestamp := reader.UnpackUint64(true)
Expand All @@ -231,14 +234,15 @@ func (i *Indexer) GetTransaction(txID ids.ID) (bool, int64, bool, fees.Dimension
for i := range outputs {
outputs[i] = reader.UnpackLimitedBytes(consts.NetworkSizeLimit)
}
errorStr := reader.UnpackString(false)
if err := reader.Err(); err != nil {
return false, 0, false, fees.Dimensions{}, 0, nil, err
return false, 0, false, fees.Dimensions{}, 0, nil, "", err
}
dimensions, err := fees.UnpackDimensions(dimensionsBytes)
if err != nil {
return false, 0, false, fees.Dimensions{}, 0, nil, err
return false, 0, false, fees.Dimensions{}, 0, nil, "", err
}
return true, int64(timestamp), success, dimensions, fee, outputs, nil
return true, int64(timestamp), success, dimensions, fee, outputs, errorStr, nil
}

func (i *Indexer) Close() error {
Expand Down
4 changes: 3 additions & 1 deletion api/indexer/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ type GetTxResponse struct {
Units fees.Dimensions `json:"units"`
Fee uint64 `json:"fee"`
Outputs []codec.Bytes `json:"result"`
ErrorStr string `json:"errorStr"`
}

type Server struct {
Expand All @@ -122,7 +123,7 @@ func (s *Server) GetTx(req *http.Request, args *GetTxRequest, reply *GetTxRespon
_, span := s.tracer.Start(req.Context(), "Indexer.GetTx")
defer span.End()

found, t, success, units, fee, outputs, err := s.indexer.GetTransaction(args.TxID)
found, t, success, units, fee, outputs, errorStr, err := s.indexer.GetTransaction(args.TxID)
if err != nil {
return err
}
Expand All @@ -139,5 +140,6 @@ func (s *Server) GetTx(req *http.Request, args *GetTxRequest, reply *GetTxRespon
wrappedOutputs[i] = codec.Bytes(output)
}
reply.Outputs = wrappedOutputs
reply.ErrorStr = errorStr
return nil
}
13 changes: 2 additions & 11 deletions api/jsonrpc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,19 +194,10 @@ func (cli *JSONRPCClient) GetABI(ctx context.Context) (abi.ABI, error) {
return resp.ABI, err
}

func (cli *JSONRPCClient) ExecuteActions(ctx context.Context, actor codec.Address, actions []chain.Action) ([][]byte, error) {
actionsMarshaled := make([][]byte, 0, len(actions))
for _, action := range actions {
actionBytes, err := chain.MarshalTyped(action)
if err != nil {
return nil, fmt.Errorf("failed to marshal action: %w", err)
}
actionsMarshaled = append(actionsMarshaled, actionBytes)
}

func (cli *JSONRPCClient) ExecuteActions(ctx context.Context, actor codec.Address, actionsBytes [][]byte) ([][]byte, error) {
args := &ExecuteActionArgs{
Actor: actor,
Actions: actionsMarshaled,
Actions: actionsBytes,
}

resp := new(ExecuteActionReply)
Expand Down
6 changes: 5 additions & 1 deletion api/ws/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,14 @@ func (c *WebSocketClient) ListenBlock(

// IssueTx sends [tx] to the streaming rpc server.
func (c *WebSocketClient) RegisterTx(tx *chain.Transaction) error {
return c.RegisterRawTx(tx.Bytes())
}

func (c *WebSocketClient) RegisterRawTx(txBytes []byte) error {
if c.closed {
return ErrClosed
}
return c.mb.Send(append([]byte{TxMode}, tx.Bytes()...))
return c.mb.Send(append([]byte{TxMode}, txBytes...))
}

// ListenTx listens for responses from the streamingServer.
Expand Down
22 changes: 20 additions & 2 deletions chain/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@ func (t *TransactionData) Sign(
return UnmarshalTx(p, actionCodec, authCodec)
}

func SignRawActionBytesTx(
base *Base,
rawActionsBytes []byte,
authFactory AuthFactory,
) ([]byte, error) {
p := codec.NewWriter(base.Size(), consts.NetworkSizeLimit)
base.Marshal(p)
p.PackFixedBytes(rawActionsBytes)

auth, err := authFactory.Sign(p.Bytes())
if err != nil {
return nil, err
}
p.PackByte(auth.GetTypeID())
auth.Marshal(p)
return p.Bytes(), p.Err()
}

func (t *TransactionData) Expiry() int64 { return t.Base.Timestamp }

func (t *TransactionData) MaxFee() uint64 { return t.Base.MaxFee }
Expand All @@ -116,7 +134,7 @@ func (t *TransactionData) Marshal(p *codec.Packer) error {

func (t *TransactionData) marshal(p *codec.Packer) error {
t.Base.Marshal(p)
return t.Actions.marshalInto(p)
return t.Actions.MarshalInto(p)
}

type Actions []Action
Expand All @@ -133,7 +151,7 @@ func (a Actions) Size() (int, error) {
return size, nil
}

func (a Actions) marshalInto(p *codec.Packer) error {
func (a Actions) MarshalInto(p *codec.Packer) error {
p.PackByte(uint8(len(a)))
for _, action := range a {
p.PackByte(action.GetTypeID())
Expand Down
Loading

0 comments on commit 960b790

Please sign in to comment.