Opcode: log#2529
Conversation
jannotti
left a comment
There was a problem hiding this comment.
I'd like to see some tests that contain multiple calls to log
|
Hello, this is really great. I have been doing my own "log()" in Tealang using get, put and concat. One thing I wonder though, why is it necessary to limit to 8 times, rather than using the existing cost system? |
After discussion on Discord, we've decided that allowing 32 calls makes sense, with the 1k limit intact. For posterity - the reason for two such limits is, as with ApplicationArgs, there is additional space overhead associated with each call, so it should not be possible to log 1000 1 byte messages, as that would consume much more than 1k. But 32 should not really make much difference. |
Codecov Report
@@ Coverage Diff @@
## master #2529 +/- ##
==========================================
- Coverage 47.16% 47.08% -0.08%
==========================================
Files 350 350
Lines 55954 56276 +322
==========================================
+ Hits 26389 26499 +110
- Misses 26620 26808 +188
- Partials 2945 2969 +24
Continue to review full report at Codecov.
|
jdtzmn
left a comment
There was a problem hiding this comment.
Looks good, just had one question.
| runMode: runModeSignature, | ||
| } | ||
|
|
||
| failCases := []failCase{failCase0, failCase1, failCase2, failCase3, failCase4, failCase5} |
There was a problem hiding this comment.
nit: A pattern we use a lot for this is an anonymous struct:
failCases := []struct {
source string
runMode runMode
errContains string
} {
// cases here.
}
jasonpaulos
left a comment
There was a problem hiding this comment.
This looks good! I just left some insignificant comments
jasonpaulos
left a comment
There was a problem hiding this comment.
One small thing I noticed
| result.LocalDeltas = &localDeltas | ||
| } | ||
|
|
||
| result.Logs = DeltaLogToLog(delta.Logs) |
There was a problem hiding this comment.
Actually this function should also convert each basics.LogItem's ID into the actual application ID. I believe it should convert 0 into appIdx (which is guaranteed to be not 0 at this point) and it should error if the input ID is not 0.
| ops, err := logic.AssembleString(` | ||
| #pragma version 5 | ||
| int 1 | ||
| loop: byte "a" |
There was a problem hiding this comment.
let's do some more fancy stuff there to ensure log values vary. Something like
dup // copy loop var
int 48 // 0x32 -> '0' char
+
itob // -> char
log
There was a problem hiding this comment.
I couldn't get the suggested teal code. I did something much simpler instead.
There was a problem hiding this comment.
the idea is to use ascii char codes to obtain new char value on each loop iteration.
There was a problem hiding this comment.
Actually itob always produces an 8 byte slice. It would be better to so something like:
byte "a"
int 0
dup2
getbyte // convert 0th byte of "a" to integer, 97
int 1
+ // add 1
setbyte // set 0th byte of "a" to the byte given by the integer on the stack (97 + 1, "b")
| a.Panics(func() { c.DelKey(getRandomAddress(a), aidx, false, key, 0) }) | ||
| a.Panics(func() { c.DelKey(addr, aidx+1, false, key, 0) }) | ||
| } | ||
| func TestCowAppendLog(t *testing.T) { |
|
|
||
| prog := `#pragma version 5 | ||
| int 1 | ||
| loop: byte "a" |
There was a problem hiding this comment.
let's check different values
There was a problem hiding this comment.
I still want to see logging 32 different values in the test:
int 0 // loop variable
loop:
dup // copy
int 97 // 0x61 'a'
+ // 97, 98, 99, ...
itob // 'a', 'b', 'c', ...
log
int 1
+
dup
int 32
<
bnz loop
There was a problem hiding this comment.
see Jason's note about getbyte above #2529 (comment)
|
|
||
| // MaxLogCalls is the largest number of log calls that may appear in | ||
| // an eval delta, used for decoding purposes. | ||
| var MaxLogCalls int |
There was a problem hiding this comment.
this needs to be const, no need to initialize it checkSetAllocBounds.
| var MaxLogCalls int | |
| const MaxLogCalls = 32 |
checkSetAllocBounds might have a check that the value is not greater than 32 (or 33 as JJ suggested)
| for i, m := range logs { | ||
| if i == 0 { | ||
| assert.Equal(t, base64.StdEncoding.EncodeToString([]byte("a")), m.Value) | ||
| } else if i == 1 { | ||
| assert.Equal(t, base64.StdEncoding.EncodeToString([]byte("b")), m.Value) | ||
| } else if i == 2 { | ||
| assert.Equal(t, base64.StdEncoding.EncodeToString([]byte("c")), m.Value) | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Calculating values looks more maintainable, check below:
| for i, m := range logs { | |
| if i == 0 { | |
| assert.Equal(t, base64.StdEncoding.EncodeToString([]byte("a")), m.Value) | |
| } else if i == 1 { | |
| assert.Equal(t, base64.StdEncoding.EncodeToString([]byte("b")), m.Value) | |
| } else if i == 2 { | |
| assert.Equal(t, base64.StdEncoding.EncodeToString([]byte("c")), m.Value) | |
| } | |
| } | |
| for i, m := range logs { | |
| assert.Equal(t, base64.StdEncoding.EncodeToString([]byte(string(rune('a' + i)))), m.Value) | |
| } |
If logs are not sorted, use sort.Slice. If logs is map (not sure) then flatten it:
type flog struct {
idx int,
li LogItem
}
for k, v := range logs {
flatlog = append(flatlog, flog{i, v})
}
sort.Slice(flatlog, func(i, j int) {return flatlog[i].idx < flatlog[j].idx})There was a problem hiding this comment.
The order of logs matter, so they should not be sorted before comparison
There was a problem hiding this comment.
well ok, then assert.Equal(t, base64.StdEncoding.EncodeToString([]byte(string(rune('a' + i)))), m.Value) will work just fine
| ops, err := logic.AssembleString(` | ||
| #pragma version 5 | ||
| int 1 | ||
| loop: byte "a" |
There was a problem hiding this comment.
the idea is to use ascii char codes to obtain new char value on each loop iteration.
algorandskiy
left a comment
There was a problem hiding this comment.
I'll incorporate these MaxLogCalls fixes...
| // MaxLogCalls is the highest allowable log messages that may appear in | ||
| // any version, used for decoding purposes. Never decrease this value. | ||
| const MaxLogCalls = 32 |
There was a problem hiding this comment.
| // MaxLogCalls is the highest allowable log messages that may appear in | |
| // any version, used for decoding purposes. Never decrease this value. | |
| const MaxLogCalls = 32 | |
| // MaxLogCalls is the highest allowable log messages that may appear in | |
| // any version, used for decoding purposes. Never decrease this value. | |
| const MaxEncodedLogCalls = 32 |
| const MaxLogSize = 1024 | ||
|
|
||
| // MaxLogCalls is the limit of total log calls during a program execution | ||
| const MaxLogCalls = config.MaxLogCalls |
There was a problem hiding this comment.
| const MaxLogCalls = config.MaxLogCalls | |
| const MaxLogCalls = 32 |
| loglen: 2, | ||
| }, | ||
| { | ||
| source: fmt.Sprintf(`%s int 1`, strings.Repeat(`byte "a logging message"; log;`, config.MaxLogCalls)), |
There was a problem hiding this comment.
| source: fmt.Sprintf(`%s int 1`, strings.Repeat(`byte "a logging message"; log;`, config.MaxLogCalls)), | |
| source: fmt.Sprintf(`%s int 1`, strings.Repeat(`byte "a logging message"; log;`, MaxLogCalls)), |
| runMode: runModeApplication, | ||
| }, | ||
| { | ||
| source: fmt.Sprintf(`%s; int 1`, strings.Repeat(`byte "a"; log;`, config.MaxLogCalls+1)), |
There was a problem hiding this comment.
| source: fmt.Sprintf(`%s; int 1`, strings.Repeat(`byte "a"; log;`, config.MaxLogCalls+1)), | |
| source: fmt.Sprintf(`%s; int 1`, strings.Repeat(`byte "a"; log;`, MaxLogCalls+1)), |
| return false | ||
| } | ||
|
|
||
| // Logs must be equal |
There was a problem hiding this comment.
You should compare the lengths of the logs before iterating over them
| d1 = EvalDelta{ | ||
| Logs: []LogItem{{ID: 0, Message: "val"}}, | ||
| } | ||
| a.True(d1.Equal(d2)) |
There was a problem hiding this comment.
Suggested test case: compare two EvalDeltas with logs [A] and [A, B] -- I believe this would currently pass because of the length issue I mentioned
| // not valid. | ||
| func (ac *ApplicationCallTxnFields) AppIDByIndex(i uint64) (basics.AppIndex, error) { | ||
|
|
||
| // Index 0 always corresponds to the sender |
There was a problem hiding this comment.
nit: sender still mentioned here and in function below
| idx, err := txn.IndexByAppID(txn.ApplicationID) | ||
| if err != nil { | ||
| return err | ||
| } |
There was a problem hiding this comment.
Just to be paranoid, could you return an error if idx != 0? It shouldn't happen, but it would be really bad if it did happen
| return false | ||
| } | ||
| for i, e := range expected { | ||
| if e.ID != actual[i].ID || e.Message != actual[i].Message { |
There was a problem hiding this comment.
nit: you could use the Equals method you made here
| a.Equal(32, len(*txn.Logs)) | ||
| for i, l := range *txn.Logs { | ||
| a.Equal(expectedAppID, l.Id) | ||
| assert.Equal(t, base64.StdEncoding.EncodeToString([]byte(string(rune('B'+i)))), l.Value) |
There was a problem hiding this comment.
nit: is it necessary to import assert instead of using a.Equal?
This PR adds a new opcode
log. It takes one "bytes" stack argument. It can be called up to 32 times in a program, and log up to a total of 1k bytes.