Skip to content
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

horizon/ingest: support parsing of new InvokeHostFunction op #4608

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b2c0839
#4604: add InvokeHostFunction support
sreuland Sep 23, 2022
573c58e
#4604: removed debugging code
sreuland Sep 23, 2022
68e6901
#4604: wip on invokehostfn integration test on create contract
sreuland Sep 27, 2022
4eec6b3
Merge remote-tracking branch 'upstream/soroban-xdr-next' into 4604_in…
sreuland Sep 27, 2022
6862e0f
#4604: wip on invokehostfn integration test renamed
sreuland Sep 27, 2022
6fe1050
Merge remote-tracking branch 'upstream/soroban-xdr-next' into 4604_in…
sreuland Sep 27, 2022
f14838f
#4604: wip integration test on InvokeHostFunction CreateContract, deb…
sreuland Sep 27, 2022
4224628
#4604: wip integration test, added docs on tx assembly specs
sreuland Sep 27, 2022
564a43f
Use real contract and fix contract id
tamirms Sep 28, 2022
101dc29
using only master account
tamirms Sep 28, 2022
940db2e
Fix contract id derivation
tamirms Sep 28, 2022
1bd8f14
#4604: removed debug code from test
sreuland Sep 28, 2022
aaff28c
#4604: fixed unit test error on case sensitive error string match
sreuland Sep 28, 2022
7e880f2
#4604: fixed unit test error on message case
sreuland Sep 28, 2022
180405a
#4604: fixed db integration test on all ops reingest
sreuland Sep 28, 2022
badfe1a
#4604: fixed case sensitive error message test assertion
sreuland Sep 29, 2022
1315f39
#4604: pr feedback, removed 'position' from details of hostfn params
sreuland Sep 29, 2022
68a1d74
#4604: pr feedback on invoke host fn operation detail param serializa…
sreuland Sep 29, 2022
3a4d1b9
#4604: added test for txbuild validate on invokehostfn
sreuland Sep 29, 2022
f8637aa
Merge remote-tracking branch 'upstream/soroban-xdr-next' into 4604_in…
sreuland Sep 29, 2022
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
4 changes: 4 additions & 0 deletions keypair/full.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ func (kp *Full) FromAddress() *FromAddress {
return newFromAddressWithPublicKey(kp.address, kp.publicKey)
}

func (kp *Full) PublicKey() ed25519.PublicKey {
return kp.publicKey
}

func (kp *Full) Hint() (r [4]byte) {
copy(r[:], kp.publicKey[28:])
return
Expand Down
22 changes: 20 additions & 2 deletions protocols/horizon/operations/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,25 @@ type LiquidityPoolWithdraw struct {
ReservesReceived []base.AssetAmount `json:"reserves_received"`
}

// InvokeHostFunction is the json resource representing a single smart contract
// function invocation operation, having type InvokeHostFunction.
//
// The model for InvokeHostFunction is intentionally simplified, Footprint
// just contains a base64 encoded string of it's xdr serialization.
type InvokeHostFunction struct {
Base
Parameters []HostFunctionParameter `json:"parameters"`
Function string `json:"function"`
Footprint string `json:"footprint"`
}

// InvokeHostFunction parameter model, intentionally simplified, Value
// just contains a base64 encoded string of the ScVal xdr serialization.
type HostFunctionParameter struct {
Value string `json:"value"`
Type string `json:"type"`
}

// Operation interface contains methods implemented by the operation types
type Operation interface {
GetBase() Base
Expand Down Expand Up @@ -573,8 +592,7 @@ func UnmarshalOperation(operationTypeID int32, dataString []byte) (ops Operation
}
ops = op
case xdr.OperationTypeInvokeHostFunction:
// TODO:
var op LiquidityPoolWithdraw
var op InvokeHostFunction
if err = json.Unmarshal(dataString, &op); err != nil {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func (operation *transactionOperationWrapper) effects() ([]effect, error) {
case xdr.OperationTypeLiquidityPoolWithdraw:
err = wrapper.addLiquidityPoolWithdrawEffect()
case xdr.OperationTypeInvokeHostFunction:
// TODO:
// TODO: https://github.com/stellar/go/issues/4585
return nil, nil
default:
return nil, fmt.Errorf("Unknown operation type: %s", op.Body.Type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -585,8 +585,28 @@ func (operation *transactionOperationWrapper) Details() (map[string]interface{},
{Asset: assetB, Amount: amount.String(receivedB)},
}
case xdr.OperationTypeInvokeHostFunction:
// TODO
op := operation.operation.Body.MustInvokeHostFunctionOp()
details["function"] = op.Function.String()
params := make([]map[string]string, 0, len(op.Parameters))

for _, param := range op.Parameters {
serializedParam := map[string]string{}
serializedParam["value"] = "n/a"
serializedParam["type"] = "n/a"

if name, ok := param.ArmForSwitch(int32(param.Type)); ok {
serializedParam["type"] = name
if raw, err := param.MarshalBinary(); err == nil {
serializedParam["value"] = base64.StdEncoding.EncodeToString(raw)
}
}
params = append(params, serializedParam)
Copy link
Contributor

Choose a reason for hiding this comment

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

is there any case where we want to add a serializedParam here that doesn't have a value ?
i.e. that's what going to happen in case MarshalBinary would return an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

there shouldn't be based on type safety of the gen'd xdr structs, but rather than ignore the runtime serialization error
in any case, I've done some conservative err handling and test coverage of that exceptional case of a malformed param - 68a1d74

}

details["parameters"] = params
if raw, err := op.Footprint.MarshalBinary(); err == nil {
details["footprint"] = base64.StdEncoding.EncodeToString(raw)
}
default:
panic(fmt.Errorf("Unknown operation type: %s", operation.OperationType()))
}
Expand Down Expand Up @@ -810,11 +830,11 @@ func (operation *transactionOperationWrapper) Participants() ([]xdr.AccountId, e
case xdr.OperationTypeLiquidityPoolDeposit:
// the only direct participant is the source_account
case xdr.OperationTypeLiquidityPoolWithdraw:
// the only direct participant is the source_account
// the only direct participant is the source_account
case xdr.OperationTypeInvokeHostFunction:
// TODO
// the only direct participant is the source_account
default:
return participants, fmt.Errorf("Unknown operation type: %s", op.Body.Type)
return participants, fmt.Errorf("unknown operation type: %s", op.Body.Type)
}

sponsor, err := operation.getSponsor()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package processors

import (
"context"
"encoding/base64"
"encoding/json"
"testing"

Expand Down Expand Up @@ -89,6 +90,111 @@ func (s *OperationsProcessorTestSuiteLedger) mockBatchInsertAdds(txs []ingest.Le
return nil
}

func (s *OperationsProcessorTestSuiteLedger) TestInvokeFunctionDetails() {
source := xdr.MustMuxedAddress("GAUJETIZVEP2NRYLUESJ3LS66NVCEGMON4UDCBCSBEVPIID773P2W6AY")

contractParamVal1 := xdr.ScSymbol("func1")
contractParamVal2 := xdr.Int32(-5)
contractParamVal3 := xdr.Uint32(6)
contractParamVal4 := xdr.Uint64(3)
scoObjectBytes := []byte{0, 1, 2}
contractParamVal5 := xdr.ScObject{
Type: xdr.ScObjectTypeScoBytes,
Bin: &scoObjectBytes,
}
contractParamVal5Addr := &contractParamVal5
contractParamVal6 := xdr.ScStaticScsTrue

ledgerKeyAccount := xdr.LedgerKeyAccount{
AccountId: source.ToAccountId(),
}

tx := ingest.LedgerTransaction{
UnsafeMeta: xdr.TransactionMeta{
V: 2,
V2: &xdr.TransactionMetaV2{},
},
}

wrapper := transactionOperationWrapper{
transaction: tx,
operation: xdr.Operation{
SourceAccount: &source,
Body: xdr.OperationBody{
Type: xdr.OperationTypeInvokeHostFunction,
InvokeHostFunctionOp: &xdr.InvokeHostFunctionOp{
Function: xdr.HostFunctionHostFnCall,
Parameters: []xdr.ScVal{
{
Type: xdr.ScValTypeScvSymbol,
Sym: &contractParamVal1,
},
{
Type: xdr.ScValTypeScvI32,
I32: &contractParamVal2,
},
{
Type: xdr.ScValTypeScvU32,
U32: &contractParamVal3,
},
{
Type: xdr.ScValTypeScvBitset,
Bits: &contractParamVal4,
},
{
Type: xdr.ScValTypeScvObject,
Obj: &contractParamVal5Addr,
},
{
Type: xdr.ScValTypeScvStatic,
Ic: &contractParamVal6,
},
{
// invalid ScVal
Type: 5555,
},
},
Footprint: xdr.LedgerFootprint{
ReadOnly: []xdr.LedgerKey{
{
Type: xdr.LedgerEntryTypeAccount,
Account: &ledgerKeyAccount,
},
},
},
},
},
},
}

details, err := wrapper.Details()
s.Assert().NoError(err)
s.Assert().Equal(details["function"].(string), "HostFunctionHostFnCall")

raw, err := wrapper.operation.Body.InvokeHostFunctionOp.Footprint.MarshalBinary()
s.Assert().NoError(err)
s.Assert().Equal(details["footprint"].(string), base64.StdEncoding.EncodeToString(raw))

serializedParams := details["parameters"].([]map[string]string)
s.assertInvokeHostFunctionParameter(serializedParams, 0, "Sym", wrapper.operation.Body.InvokeHostFunctionOp.Parameters[0])
s.assertInvokeHostFunctionParameter(serializedParams, 1, "I32", wrapper.operation.Body.InvokeHostFunctionOp.Parameters[1])
s.assertInvokeHostFunctionParameter(serializedParams, 2, "U32", wrapper.operation.Body.InvokeHostFunctionOp.Parameters[2])
s.assertInvokeHostFunctionParameter(serializedParams, 3, "Bits", wrapper.operation.Body.InvokeHostFunctionOp.Parameters[3])
s.assertInvokeHostFunctionParameter(serializedParams, 4, "Obj", wrapper.operation.Body.InvokeHostFunctionOp.Parameters[4])
s.assertInvokeHostFunctionParameter(serializedParams, 5, "Ic", wrapper.operation.Body.InvokeHostFunctionOp.Parameters[5])
s.assertInvokeHostFunctionParameter(serializedParams, 6, "n/a", wrapper.operation.Body.InvokeHostFunctionOp.Parameters[6])
}

func (s *OperationsProcessorTestSuiteLedger) assertInvokeHostFunctionParameter(parameters []map[string]string, paramPosition int, expectedType string, expectedVal xdr.ScVal) {
serializedParam := parameters[paramPosition]
s.Assert().Equal(serializedParam["type"], expectedType)
if expectedSerializedXdr, err := expectedVal.MarshalBinary(); err == nil {
s.Assert().Equal(serializedParam["value"], base64.StdEncoding.EncodeToString(expectedSerializedXdr))
} else {
s.Assert().Equal(serializedParam["value"], "n/a")
}
}

func (s *OperationsProcessorTestSuiteLedger) TestAddOperationSucceeds() {
unmuxed := xdr.MustAddress("GA5WBPYA5Y4WAEHXWR2UKO2UO4BUGHUQ74EUPKON2QHV4WRHOIRNKKH2")
muxed := xdr.MuxedAccount{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type StatsLedgerTransactionProcessorResults struct {
OperationsSetTrustLineFlags int64
OperationsLiquidityPoolDeposit int64
OperationsLiquidityPoolWithdraw int64
OperationsInvokeHostFunction int64
}

func (p *StatsLedgerTransactionProcessor) ProcessTransaction(ctx context.Context, transaction ingest.LedgerTransaction) error {
Expand Down Expand Up @@ -116,7 +117,7 @@ func (p *StatsLedgerTransactionProcessor) ProcessTransaction(ctx context.Context
case xdr.OperationTypeLiquidityPoolWithdraw:
p.results.OperationsLiquidityPoolWithdraw++
case xdr.OperationTypeInvokeHostFunction:
// TODO
p.results.OperationsInvokeHostFunction++
return nil
default:
panic(fmt.Sprintf("Unkown operation type: %d", op.Body.Type))
Expand Down Expand Up @@ -164,6 +165,7 @@ func (stats *StatsLedgerTransactionProcessorResults) Map() map[string]interface{
"stats_operations_clawback_claimable_balance": stats.OperationsClawbackClaimableBalance,
"stats_operations_liquidity_pool_deposit": stats.OperationsLiquidityPoolDeposit,
"stats_operations_liquidity_pool_withdraw": stats.OperationsLiquidityPoolWithdraw,
"stats_operations_invoke_host_function": stats.OperationsInvokeHostFunction,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2216,7 +2216,7 @@ func TestParticipantsCoversAllOperationTypes(t *testing.T) {
}
// calling Participants should error due to the unknown operation
_, err := operation.Participants()
assert.Contains(t, err.Error(), "Unknown operation type")
assert.Contains(t, err.Error(), "unknown operation type")
}

func TestDetailsCoversAllOperationTypes(t *testing.T) {
Expand Down
32 changes: 25 additions & 7 deletions services/horizon/internal/integration/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,19 @@ func submitPaymentOps(itest *integration.Test, tt *assert.Assertions) (submitted
return ops, txResp.Ledger
}

func submitInvokeHostFunction(itest *integration.Test, tt *assert.Assertions) (submittedOperations []txnbuild.Operation, lastLedger int32) {
ops := []txnbuild.Operation{
&txnbuild.InvokeHostFunction{
Function: xdr.HostFunctionHostFnCall,
Copy link
Contributor

Choose a reason for hiding this comment

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

should these values be populated?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in this test coverage, success/fail of tx was not scope for assertion, rather, it's just the presence of the InvokeHostFunction xdr going through ingest, so tried to avoid further complexity on that.

Footprint: xdr.LedgerFootprint{},
Parameters: xdr.ScVec{},
},
}
txResp, _ := itest.SubmitOperations(itest.MasterAccount(), itest.Master(), ops...)

return ops, txResp.Ledger
}

func submitSponsorshipOps(itest *integration.Test, tt *assert.Assertions) (submittedOperations []txnbuild.Operation, lastLedger int32) {
keys, accounts := itest.CreateAccounts(1, "1000")
sponsor, sponsorPair := accounts[0], keys[0]
Expand Down Expand Up @@ -400,6 +413,12 @@ func initializeDBIntegrationTest(t *testing.T) (itest *integration.Test, reached
itest = integration.NewTest(t, integration.Config{})
tt := assert.New(t)

// Make sure all possible operations are covered by reingestion
allOpTypes := set.Set[xdr.OperationType]{}
for typ := range xdr.OperationTypeToStringMap {
allOpTypes.Add(xdr.OperationType(typ))
}

// submit all possible operations
ops, _ := submitAccountOps(itest, tt)
submittedOps := ops
Expand All @@ -415,16 +434,15 @@ func initializeDBIntegrationTest(t *testing.T) (itest *integration.Test, reached
submittedOps = append(submittedOps, ops...)
ops, reachedLedger = submitLiquidityPoolOps(itest, tt)
submittedOps = append(submittedOps, ops...)

// Make sure all possible operations are covered by reingestion
allOpTypes := set.Set[xdr.OperationType]{}
for typ := range xdr.OperationTypeToStringMap {
allOpTypes.Add(xdr.OperationType(typ))
if integration.GetCoreMaxSupportedProtocol() > 19 {
ops, _ = submitInvokeHostFunction(itest, tt)
submittedOps = append(submittedOps, ops...)
} else {
delete(allOpTypes, xdr.OperationTypeInvokeHostFunction)
}

// Inflation is not supported
delete(allOpTypes, xdr.OperationTypeInflation)
// TODO:
delete(allOpTypes, xdr.OperationTypeInvokeHostFunction)

for _, op := range submittedOps {
opXDR, err := op.BuildXDR()
Expand Down
Loading