-
Notifications
You must be signed in to change notification settings - Fork 8
indexer: identify DEPLOY
state changes
#252
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
base: main
Are you sure you want to change the base?
Changes from all commits
65e2dcb
07e412b
595deb4
4911e6f
981c4db
0b8f713
d0114f8
c7704e9
f965581
fe12025
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package processors | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
operation_processor "github.com/stellar/go/processors/operation" | ||
"github.com/stellar/go/xdr" | ||
|
||
"github.com/stellar/wallet-backend/internal/indexer/types" | ||
) | ||
|
||
// ContractDeployProcessor emits state changes for contract deployments. | ||
type ContractDeployProcessor struct { | ||
networkPassphrase string | ||
} | ||
|
||
func NewContractDeployProcessor(networkPassphrase string) *ContractDeployProcessor { | ||
return &ContractDeployProcessor{networkPassphrase: networkPassphrase} | ||
} | ||
|
||
// ProcessOperation emits a state change for each contract deployment (including subinvocations). | ||
func (p *ContractDeployProcessor) ProcessOperation(_ context.Context, op *operation_processor.TransactionOperationWrapper) ([]types.StateChange, error) { | ||
if op.OperationType() != xdr.OperationTypeInvokeHostFunction { | ||
return nil, ErrInvalidOpType | ||
} | ||
invokeHostOp := op.Operation.Body.MustInvokeHostFunctionOp() | ||
|
||
opID := op.ID() | ||
builder := NewStateChangeBuilder(op.Transaction.Ledger.LedgerSequence(), op.LedgerClosed.Unix(), op.Transaction.Hash.HexString()). | ||
WithOperationID(opID). | ||
WithCategory(types.StateChangeCategoryContract). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know if "contract" is the best category we can use. We're only indexing contract deployments for contracts that are registered as accounts, right? Meaning, the flow would be:
This to me seems like the contract version of a CreateAccount operation. Which, speaking of which, I don't think we have a state change for an account being created, right? Should we rename this so that creation events for both stellar accounts and contracts result in the same state change? Of course, stellar accounts being created will also result in a credit XLM state change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ![]() (G) Account creation is generating these state changes, but for contracts there's no concept of DEBIT/CREDIT in play, it's just fees and deployer so I think the states being changed are different even though they can both be interpreted as account creation. I'm still inclined to keep some differentiation in G-account and C-account creation because the nature of the accounts are different. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, well even if we keep a “Contract”/“Deploy” classification/reason for contract account creation, we don’t have a state change for stellar account creation. Meaning, a client querying for state changes wouldn’t be able to tell when the account was created. They’d have to inspect the operation or transaction. If we have a state change representing account creation for contracts, I think we should have one for classic accounts. Wdyt? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that makes sense to me 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In which case, we probably can create a new ACCOUNT_CREATION category that applies for both, and then we use that instead of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SGTM! |
||
WithReason(types.StateChangeReasonDeploy) | ||
|
||
deployedContractsMap := map[string]types.StateChange{} | ||
|
||
processCreate := func(fromAddr xdr.ContractIdPreimageFromAddress) error { | ||
marcelosalloum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
contractID, err := calculateContractID(p.networkPassphrase, fromAddr) | ||
if err != nil { | ||
return fmt.Errorf("calculating contract ID: %w", err) | ||
} | ||
deployerAddr, err := fromAddr.Address.String() | ||
if err != nil { | ||
return fmt.Errorf("deployer address to string: %w", err) | ||
} | ||
|
||
deployedContractsMap[contractID] = builder.Clone().WithAccount(contractID).WithDeployer(deployerAddr).Build() | ||
return nil | ||
} | ||
|
||
var walkInvocation func(inv xdr.SorobanAuthorizedInvocation) error | ||
walkInvocation = func(inv xdr.SorobanAuthorizedInvocation) error { | ||
JakeUrban marked this conversation as resolved.
Show resolved
Hide resolved
|
||
switch inv.Function.Type { | ||
case xdr.SorobanAuthorizedFunctionTypeSorobanAuthorizedFunctionTypeCreateContractHostFn: | ||
cc := inv.Function.MustCreateContractHostFn() | ||
if cc.ContractIdPreimage.Type == xdr.ContractIdPreimageTypeContractIdPreimageFromAddress { | ||
if err := processCreate(cc.ContractIdPreimage.MustFromAddress()); err != nil { | ||
return err | ||
} | ||
} | ||
case xdr.SorobanAuthorizedFunctionTypeSorobanAuthorizedFunctionTypeCreateContractV2HostFn: | ||
cc := inv.Function.MustCreateContractV2HostFn() | ||
if cc.ContractIdPreimage.Type == xdr.ContractIdPreimageTypeContractIdPreimageFromAddress { | ||
if err := processCreate(cc.ContractIdPreimage.MustFromAddress()); err != nil { | ||
return err | ||
} | ||
} | ||
case xdr.SorobanAuthorizedFunctionTypeSorobanAuthorizedFunctionTypeContractFn: | ||
// no-op | ||
} | ||
for _, sub := range inv.SubInvocations { | ||
if err := walkInvocation(sub); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
hf := invokeHostOp.HostFunction | ||
switch hf.Type { | ||
case xdr.HostFunctionTypeHostFunctionTypeCreateContract: | ||
cc := hf.MustCreateContract() | ||
if cc.ContractIdPreimage.Type == xdr.ContractIdPreimageTypeContractIdPreimageFromAddress { | ||
if err := processCreate(cc.ContractIdPreimage.MustFromAddress()); err != nil { | ||
return nil, err | ||
} | ||
} | ||
case xdr.HostFunctionTypeHostFunctionTypeCreateContractV2: | ||
cc := hf.MustCreateContractV2() | ||
if cc.ContractIdPreimage.Type == xdr.ContractIdPreimageTypeContractIdPreimageFromAddress { | ||
if err := processCreate(cc.ContractIdPreimage.MustFromAddress()); err != nil { | ||
return nil, err | ||
} | ||
} | ||
case xdr.HostFunctionTypeHostFunctionTypeUploadContractWasm, xdr.HostFunctionTypeHostFunctionTypeInvokeContract: | ||
// no-op | ||
} | ||
|
||
for _, auth := range invokeHostOp.Auth { | ||
if err := walkInvocation(auth.RootInvocation); err != nil { | ||
return nil, err | ||
} | ||
} | ||
JakeUrban marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
stateChanges := make([]types.StateChange, 0, len(deployedContractsMap)) | ||
for _, sc := range deployedContractsMap { | ||
stateChanges = append(stateChanges, sc) | ||
} | ||
return stateChanges, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small design idea: since we are using the same interface here, we can instead have a list with these two processors and use a for loop to push the state changes. So something like this:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with that change but I think it's outside the scope of this PR. It should be done in a dedicated PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I opened a PR here, but in reality this won't scale for more custom contract processors.
This discussion points to the necessity of calling RPC to assert contract interfaces and likely adding a special cache for contracts. This cache can expand a lot and we'll probably need some space-efficient filter, like BloomFilter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm leaving that PR in draft. Feel free to either close it or build on top of it.