gnolang / gno

Gno: An interpreted, stack-based Go virtual machine to build succinct and composable apps + Gno.land: a blockchain for timeless code and fair open-source.
https://gno.land/
Other
882 stars 366 forks source link

gnovm(bug): time.Time rendering causes Machine Panic #1588

Closed leohhhn closed 7 months ago

leohhhn commented 8 months ago

While working on the rendering of the blog realm, I came upon a possible Gno bug. Specifically, the bug happens when trying to return the value of a time.Time, formatted to the RFC3339 format using .Format().

Take a look at the following example realm.

package tt

import (
    "gno.land/p/demo/avl"
    "gno.land/p/demo/ufmt"
    "time"
)

var (
    storageTree avl.Tree
    index       = 0
)

type Post struct {
    CreatedAt time.Time
}

func Store(date string) {
    var parsedTime time.Time
    var err error

    if date != "" {
        parsedTime, err = time.Parse(time.RFC3339, date)

        if err != nil {
            panic(err)
        }
    } else {
        parsedTime = time.Now()
    }

    post := &Post{
        CreatedAt: parsedTime,
    }

    storageTree.Set(ufmt.Sprintf("%d", index), post)
    index++
}

func Read() string {
    out := ""

    for i := 0; i < index; i++ {
        rawPost, _ := storageTree.Get(ufmt.Sprintf("%d", i))
        post := rawPost.(*Post)

        out += post.CreatedAt.Format(time.RFC3339)
        out += "\n\n"
    }

    return out
} 
^^^ Error happens when calling Read() 

Calling the following commands:

// time.Now() as parsed time
❯ gnokey maketx call -pkgpath gno.land/r/demo/tt --func Store --args "" --gas-fee 10000000ugnot --gas-wanted 800000  --broadcast dev
Enter password.

OK!
GAS WANTED: 800000
GAS USED:   492640

// Rendering is OK, no error
❯ gnokey maketx call -pkgpath gno.land/r/demo/tt --func Read --gas-fee 10000000ugnot --gas-wanted 800000  --broadcast dev           
Enter password.
("2024-01-25T20:52:14Z\n\n" string)
OK!
GAS WANTED: 800000
GAS USED:   398033

// 2024-01-24T13:55:00Z as parsed time, a valid RFC3339 format
❯ gnokey maketx call -pkgpath gno.land/r/demo/tt --func Store --args "2024-01-24T13:55:00Z" --gas-fee 10000000ugnot --gas-wanted 800000  --broadcast dev
Enter password.

OK!
GAS WANTED: 800000
GAS USED:   555203

// Rendering is OK, no error
❯ gnokey maketx call -pkgpath gno.land/r/demo/tt --func Read --gas-fee 10000000ugnot --gas-wanted 800000  --broadcast dev                               
Enter password.
("2024-01-25T20:52:14Z\n\n2024-01-24T13:55:00Z\n\n" string)
OK!
GAS WANTED: 800000
GAS USED:   408777

// Finally, for 2024-01-25T21:28:15+01:00, also a valid RFC3339 fromat
❯ gnokey maketx call -pkgpath gno.land/r/demo/tt --func Store --args "2024-01-25T21:28:15+01:00" --gas-fee 10000000ugnot --gas-wanted 800000  --broadcast dev
Enter password.

OK!
GAS WANTED: 800000
GAS USED:   651010

❯ gnokey maketx call -pkgpath gno.land/r/demo/tt --func Read --gas-fee 10000000ugnot --gas-wanted 800000  --broadcast dev                                    
Enter password.
--= Error =--

Here is a gist of the error. I am sure the formats passed in as strings are valid in Go: https://go.dev/play/p/IFMutN8aWGV

Running a vm/qeval returns a 500 internal server error:

❯ gnokey query vm/qeval --data "gno.land/r/demo/tt Read()"                                                               
--= Error =--
Data: server at 'http://127.0.0.1:26657' returned 500 Internal Server Error
--= /Error =--

What might be causing this?

cc @thehowl

thehowl commented 8 months ago

full trace result and txtar test: https://gist.github.com/thehowl/f0ca7715705998116c2c88a6811a692f

This seems to be related to realm storage of time.Time values which have a Location value of the timezone. Needs some more investigation.

Specifically, the machine info seems to point to the error happening somewhere here:

https://github.com/gnolang/gno/blob/b6193518e278cc6d83d56a5a84f69fb778bbcd04/gnovm/stdlibs/time/time.gno#L467-L468

deelawn commented 8 months ago

You're spot on with the line in question @thehowl. It's resolving the l.cacheZone selector that is causing the problem. For some reason, reading that object from storage is returning an array typed value rather than the expected struct. So far it's unclear to me if something got mucked up in how this data is being read or how the data was saved. I'll take a closer look at it.