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

Enable debugging itxn programs #2900

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 4 additions & 2 deletions cmd/tealdbg/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func protoFromString(protoString string) (name string, proto config.ConsensusPar
var ok bool
proto, ok = config.Consensus[protocol.ConsensusVersion(protoString)]
if !ok {
err = fmt.Errorf("Unknown protocol %s", protoString)
err = fmt.Errorf("unknown protocol %s", protoString)
return
}
name = protoString
Expand Down Expand Up @@ -369,7 +369,7 @@ func (r *LocalRunner) Setup(dp *DebugParams) (err error) {
source := string(data)
ops, err := logic.AssembleString(source)
if ops.Version > r.proto.LogicSigVersion {
return fmt.Errorf("Program version (%d) is beyond the maximum supported protocol version (%d)", ops.Version, r.proto.LogicSigVersion)
return fmt.Errorf("program version (%d) is beyond the maximum supported protocol version (%d)", ops.Version, r.proto.LogicSigVersion)
}
if err != nil {
errorLines := ""
Expand Down Expand Up @@ -534,6 +534,7 @@ func (r *LocalRunner) RunAll() error {
TxnGroup: r.txnGroup,
GroupIndex: run.groupIndex,
PastSideEffects: run.pastSideEffects,
Specials: &transactions.SpecialAddresses{},
}

run.result.pass, run.result.err = run.eval(ep)
Expand Down Expand Up @@ -562,6 +563,7 @@ func (r *LocalRunner) Run() (bool, error) {
TxnGroup: r.txnGroup,
GroupIndex: run.groupIndex,
PastSideEffects: run.pastSideEffects,
Specials: &transactions.SpecialAddresses{},
}

// Workaround for Go's nil/empty interfaces nil check after nil assignment, i.e.
Expand Down
74 changes: 74 additions & 0 deletions cmd/tealdbg/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1064,3 +1064,77 @@ func TestLocalBalanceAdapterIndexer(t *testing.T) {
ba := l.runs[0].ba
checkBalanceAdapter(a, ba, sender, payTxn.Txn.Receiver, assetIdx, appIdx)
}

func TestDebugTxSubmit(t *testing.T) {
partitiontest.PartitionTest(t)
a := require.New(t)

sender, err := basics.UnmarshalChecksumAddress("47YPQTIGQEO7T4Y4RWDYWEKV6RTR2UNBQXBABEEGM72ESWDQNCQ52OPASU")
a.NoError(err)
app, err := basics.UnmarshalChecksumAddress("6BPQU5WNZMTO4X72A2THZCGNJNTTE7YL6AWCYSUUTZEIYMJSEPJCQQ6DQI")
a.NoError(err)

// make balance records
assetIdx := basics.AssetIndex(50)
appIdx := basics.AppIndex(100)
brs := makeSampleBalanceRecord(sender, assetIdx, appIdx)
bra := makeSampleBalanceRecord(app, assetIdx, appIdx)
balanceBlob := protocol.EncodeMsgp(&brs)
balanceBlob = append(balanceBlob, protocol.EncodeMsgp(&bra)...)

txn := transactions.SignedTxn{
Txn: transactions.Transaction{
Type: protocol.ApplicationCallTx,
Header: transactions.Header{
Sender: sender,
Fee: basics.MicroAlgos{Raw: 100},
Note: []byte{1, 2, 3},
},
ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{
ApplicationID: appIdx,
},
},
}

txnEnc := protocol.EncodeJSON(&txn)
txnBlob := []byte("[" + strings.Join([]string{string(txnEnc), txnSample}, ",") + "]")

source := `#pragma version 5
itxn_begin
int acfg
itxn_field TypeEnum
int 1000000
itxn_field ConfigAssetTotal
int 3
itxn_field ConfigAssetDecimals
byte "oz"
itxn_field ConfigAssetUnitName
byte "Gold"
itxn_field ConfigAssetName
byte "https://gold.rush/"
itxn_field ConfigAssetURL
byte 0x67f0cd61653bd34316160bc3f5cd3763c85b114d50d38e1f4e72c3b994411e7b
itxn_field ConfigAssetMetadataHash
itxn_submit
int 1`

ds := DebugParams{
ProgramNames: []string{"test"},
ProgramBlobs: [][]byte{[]byte(source)},
BalanceBlob: balanceBlob,
TxnBlob: txnBlob,
Proto: string(protocol.ConsensusCurrentVersion),
Round: 222,
LatestTimestamp: 333,
GroupIndex: 0,
RunMode: "application",
}

local := MakeLocalRunner(nil) // no debugger
err = local.Setup(&ds)
a.NoError(err)

pass, err := local.Run()
a.NoError(err)
a.True(pass)
}
40 changes: 3 additions & 37 deletions daemon/algod/api/server/v2/dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (dr *DryrunRequest) ExpandSources() error {
for i, s := range dr.Sources {
ops, err := logic.AssembleString(s.Source)
if err != nil {
return fmt.Errorf("Dryrun Source[%d]: %v", i, err)
return fmt.Errorf("dryrun Source[%d]: %v", i, err)
}
switch s.FieldName {
case "lsig":
Expand All @@ -104,7 +104,7 @@ func (dr *DryrunRequest) ExpandSources() error {
}
}
default:
return fmt.Errorf("Dryrun Source[%d]: bad field name %#v", i, s.FieldName)
return fmt.Errorf("dryrun Source[%d]: bad field name %#v", i, s.FieldName)
}
}
return nil
Expand Down Expand Up @@ -338,41 +338,6 @@ func (dl *dryrunLedger) GetCreatorForRound(rnd basics.Round, cidx basics.Creatab
return basics.Address{}, false, fmt.Errorf("unknown creatable type %d", ctype)
}

func (dl *dryrunLedger) getAppParams(addr basics.Address, aidx basics.AppIndex) (params basics.AppParams, err 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.

removed some unused code

idx, ok := dl.accountApps[addr]
if !ok {
err = fmt.Errorf("addr %s is not know to dryrun", addr.String())
return
}
if aidx != basics.AppIndex(dl.dr.Apps[idx].Id) {
err = fmt.Errorf("creator addr %s does not match to app id %d", addr.String(), aidx)
return
}
if params, err = ApplicationParamsToAppParams(&dl.dr.Apps[idx].Params); err != nil {
return
}
return
}

func (dl *dryrunLedger) getLocalKV(addr basics.Address, aidx basics.AppIndex) (kv basics.TealKeyValue, err error) {
idx, ok := dl.accountsIn[addr]
if !ok {
err = fmt.Errorf("addr %s is not know to dryrun", addr.String())
return
}
var ad basics.AccountData
if ad, err = AccountToAccountData(&dl.dr.Accounts[idx]); err != nil {
return
}
loc, ok := ad.AppLocalStates[aidx]
if !ok {
err = fmt.Errorf("addr %s not opted in to app %d, cannot fetch state", addr.String(), aidx)
return
}
kv = loc.KeyValue
return
}

func makeBalancesAdapter(dl *dryrunLedger, txn *transactions.Transaction, appIdx basics.AppIndex) (ba apply.Balances, err error) {
ba = ledger.MakeDebugBalances(dl, basics.Round(dl.dr.Round), protocol.ConsensusVersion(dl.dr.ProtocolVersion), dl.dr.LatestTimestamp)

Expand Down Expand Up @@ -426,6 +391,7 @@ func doDryrunRequest(dr *DryrunRequest, response *generated.DryrunResponse) {
GroupIndex: uint64(ti),
PastSideEffects: pse,
PooledApplicationBudget: &pooledAppBudget,
Specials: &transactions.SpecialAddresses{},
}
var result generated.DryrunTxnResult
if len(stxn.Lsig.Logic) > 0 {
Expand Down
95 changes: 94 additions & 1 deletion daemon/algod/api/server/v2/dryrun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,7 @@ func TestDryrunEncodeDecode(t *testing.T) {
dr1, err := DryrunRequestFromGenerated(&gdr)
require.NoError(t, err)
encoded, err = encode(protocol.CodecHandle, &dr)
require.NoError(t, err)
encoded2 := protocol.EncodeReflect(&dr)
require.Equal(t, encoded, encoded2)

Expand All @@ -840,7 +841,6 @@ func TestDryrunEncodeDecode(t *testing.T) {
require.NoError(t, err)
require.Equal(t, dr1, dr2)

dec = protocol.NewDecoder(buf)
dr2 = DryrunRequest{}
err = decode(protocol.CodecHandle, encoded, &dr2)
require.NoError(t, err)
Expand Down Expand Up @@ -1136,6 +1136,7 @@ return
require.NoError(t, err)
approval := ops.Program
ops, err = logic.AssembleString("int 1")
require.NoError(t, err)
clst := ops.Program
ops, err = logic.AssembleString("#pragma version 5 \nint 1")
approv := ops.Program
Expand Down Expand Up @@ -1332,6 +1333,98 @@ func TestDryrunCost(t *testing.T) {
}
}

func TestDebugTxSubmit(t *testing.T) {
partitiontest.PartitionTest(t)
a := require.New(t)
source := `#pragma version 5
itxn_begin
int acfg
itxn_field TypeEnum
int 1000000
itxn_field ConfigAssetTotal
int 3
itxn_field ConfigAssetDecimals
byte "oz"
itxn_field ConfigAssetUnitName
byte "Gold"
itxn_field ConfigAssetName
byte "https://gold.rush/"
itxn_field ConfigAssetURL
byte 0x67f0cd61653bd34316160bc3f5cd3763c85b114d50d38e1f4e72c3b994411e7b
itxn_field ConfigAssetMetadataHash
itxn_submit
int 1`

ops, err := logic.AssembleString(source)
require.NoError(t, err)
approval := ops.Program

ops, err = logic.AssembleString("int 1")
clst := ops.Program
require.NoError(t, err)

sender, err := basics.UnmarshalChecksumAddress("47YPQTIGQEO7T4Y4RWDYWEKV6RTR2UNBQXBABEEGM72ESWDQNCQ52OPASU")
a.NoError(err)
app, err := basics.UnmarshalChecksumAddress("6BPQU5WNZMTO4X72A2THZCGNJNTTE7YL6AWCYSUUTZEIYMJSEPJCQQ6DQI")
a.NoError(err)

// make balance records
appIdx := basics.AppIndex(100)
dr := DryrunRequest{
Txns: []transactions.SignedTxn{{
Txn: transactions.Transaction{
Type: protocol.ApplicationCallTx,
Header: transactions.Header{
Sender: sender,
Fee: basics.MicroAlgos{Raw: 100},
Note: []byte{1, 2, 3},
},
ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{
ApplicationID: appIdx,
},
},
}},
Apps: []generated.Application{
{
Id: uint64(appIdx),
Params: generated.ApplicationParams{
Creator: sender.String(),
ApprovalProgram: approval,
ClearStateProgram: clst,
},
},
},
Accounts: []generated.Account{
{
Address: sender.String(),
Status: "Online",
Amount: 10000000,
AmountWithoutPendingRewards: 10000000,
},
{
Address: app.String(),
Status: "Offline",
Amount: 10000000,
AmountWithoutPendingRewards: 10000000,
},
{
Address: basics.Address{}.String(),
Status: "Offline",
},
},
}

dr.ProtocolVersion = string(dryrunProtoVersion)

var response generated.DryrunResponse
doDryrunRequest(&dr, &response)
require.NoError(t, err)
checkAppCallPass(t, &response)
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like we neglected to add inner transactions to the DryrunTxnResult API type. @jannotti was there a reason for this, or did we just forgot about it?

Ideally any created inner transactions would be returned in that response and this test could verify they are there and properly formatted.

Copy link
Contributor

Choose a reason for hiding this comment

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

Definitely not on purpose.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am incapable of expressing how much I hate that dryrun lives in its own universe where we have to do everything all over again.

if t.Failed() {
logResponse(t, &response)
}
}

func TestDryrunBalanceWithReward(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
Expand Down
2 changes: 1 addition & 1 deletion test/e2e-go/cli/tealdbg/expect/tealdbgTest.exp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ if { [catch {
spawn tealdbg debug -v $TEAL_PROG_FILE -p $PROTOCOL_VERSION_2 --remote-debugging-port 9392 --listen 127.0.0.1
expect {
timeout { puts "tealdbg debug timed out"; exit 1 }
-re {Debug error: Program version \([0-9]+\) is beyond the maximum supported protocol version \([0-9]+\)} { set FAILED 1; close }
-re {Debug error: program version \([0-9]+\) is beyond the maximum supported protocol version \([0-9]+\)} { set FAILED 1; close }
}
if { $FAILED == 0 } {
puts "ERROR: the command should have failed"
Expand Down