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

fix(gnodev): timestamp issue on reload #2943

Merged
merged 13 commits into from
Nov 27, 2024
2 changes: 1 addition & 1 deletion contribs/gnodev/cmd/gnodev/setup_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func setupDevNode(
nodeConfig.InitialTxs[index] = nodeTx
}

logger.Info("genesis file loaded", "path", devCfg.genesisFile, "txs", len(nodeConfig.InitialTxs))
logger.Info("genesis file loaded", "path", devCfg.genesisFile, "txs", len(stateTxs))
}

return gnodev.NewDevNode(ctx, nodeConfig)
Expand Down
22 changes: 16 additions & 6 deletions contribs/gnodev/pkg/dev/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"path/filepath"
"strings"
"sync"
"time"
"unicode"

"github.com/gnolang/gno/contribs/gnodev/pkg/emitter"
Expand Down Expand Up @@ -84,6 +85,9 @@
// keep track of number of loaded package to be able to skip them on restore
loadedPackages int

// track starting time for genesis
startTime time.Time

// state
initialState, state []gnoland.TxWithMetadata
currentStateIndex int
Expand All @@ -97,7 +101,8 @@
return nil, fmt.Errorf("unable map pkgs list: %w", err)
}

pkgsTxs, err := mpkgs.Load(DefaultFee)
startTime := time.Now()
pkgsTxs, err := mpkgs.Load(DefaultFee, startTime)
if err != nil {
return nil, fmt.Errorf("unable to load genesis packages: %w", err)
}
Expand All @@ -110,6 +115,7 @@
pkgs: mpkgs,
logger: cfg.Logger,
loadedPackages: len(pkgsTxs),
startTime: startTime,
state: cfg.InitialTxs,
initialState: cfg.InitialTxs,
currentStateIndex: len(cfg.InitialTxs),
Expand Down Expand Up @@ -173,9 +179,10 @@

txs := make([]gnoland.TxWithMetadata, len(b.Block.Data.Txs))
for i, encodedTx := range b.Block.Data.Txs {
// fallback on std tx
var tx std.Tx
if unmarshalErr := amino.Unmarshal(encodedTx, &tx); unmarshalErr != nil {
return nil, fmt.Errorf("unable to unmarshal amino tx, %w", unmarshalErr)
return nil, fmt.Errorf("unable to unmarshal tx: %w", err)

Check warning on line 185 in contribs/gnodev/pkg/dev/node.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnodev/pkg/dev/node.go#L185

Added line #L185 was not covered by tests
}

txs[i] = gnoland.TxWithMetadata{
Expand Down Expand Up @@ -268,8 +275,11 @@
return fmt.Errorf("unable to stop the node: %w", err)
}

// Reset starting time
startTime := time.Now()

// Generate a new genesis state based on the current packages
pkgsTxs, err := n.pkgs.Load(DefaultFee)
pkgsTxs, err := n.pkgs.Load(DefaultFee, startTime)
if err != nil {
return fmt.Errorf("unable to load pkgs: %w", err)
}
Expand All @@ -289,6 +299,7 @@

n.loadedPackages = len(pkgsTxs)
n.currentStateIndex = len(n.initialState)
n.startTime = startTime
n.emitter.Emit(&events.Reset{})
return nil
}
Expand Down Expand Up @@ -358,7 +369,6 @@
genesis := n.GenesisDoc().AppState.(gnoland.GnoGenesisState)

initialTxs := genesis.Txs[n.loadedPackages:] // ignore previously loaded packages

state := append([]gnoland.TxWithMetadata{}, initialTxs...)

lastBlock := n.getLatestBlockNumber()
Expand Down Expand Up @@ -397,7 +407,7 @@
// If NoReplay is true, simply reset the node to its initial state
n.logger.Warn("replay disabled")

txs, err := n.pkgs.Load(DefaultFee)
txs, err := n.pkgs.Load(DefaultFee, n.startTime)

Check warning on line 410 in contribs/gnodev/pkg/dev/node.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnodev/pkg/dev/node.go#L410

Added line #L410 was not covered by tests
if err != nil {
return fmt.Errorf("unable to load pkgs: %w", err)
}
Expand All @@ -413,7 +423,7 @@
}

// Load genesis packages
pkgsTxs, err := n.pkgs.Load(DefaultFee)
pkgsTxs, err := n.pkgs.Load(DefaultFee, n.startTime)
if err != nil {
return fmt.Errorf("unable to load pkgs: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion contribs/gnodev/pkg/dev/node_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func (n *Node) MoveBy(ctx context.Context, x int) error {
}

// Load genesis packages
pkgsTxs, err := n.pkgs.Load(DefaultFee)
pkgsTxs, err := n.pkgs.Load(DefaultFee, n.startTime)
if err != nil {
return fmt.Errorf("unable to load pkgs: %w", err)
}
Expand Down
121 changes: 121 additions & 0 deletions contribs/gnodev/pkg/dev/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package dev

import (
"context"
"encoding/json"
"os"
"path/filepath"
"testing"
"time"

mock "github.com/gnolang/gno/contribs/gnodev/internal/mock"

Expand Down Expand Up @@ -221,6 +223,120 @@ func Render(_ string) string { return str }
assert.Equal(t, mock.EvtNull, emitter.NextEvent().Type())
}

func TestTxTimestampRecover(t *testing.T) {
const (
// foo package
foobarGnoMod = "module gno.land/r/dev/foo\n"
fooFile = `package foo
import (
"strconv"
"strings"
"time"
)

// First time element should be init at genesis
var times = []time.Time{time.Now()}

func SpanTime() {
times = append(times, time.Now())
}

func Render(_ string) string {
var strs strings.Builder

strs.WriteRune('[')
for i, t := range times {
if i > 0 {
strs.WriteRune(',')
}
strs.WriteString(strconv.Itoa(int(t.UnixNano())))
}
strs.WriteRune(']')

return strs.String()
}
`
)

parseJSONTimesList := func(t *testing.T, render string) []time.Time {
t.Helper()

var times []time.Time
var nanos []int64

err := json.Unmarshal([]byte(render), &nanos)
require.NoError(t, err)

for _, nano := range nanos {
sec, nsec := nano/int64(time.Second), nano%int64(time.Second)
times = append(times, time.Unix(sec, nsec))
}

return times
}

// Generate package foo
foopkg := generateTestingPackage(t, "gno.mod", foobarGnoMod, "foo.gno", fooFile)

// Call NewDevNode with no package should work
node, emitter := newTestingDevNode(t, foopkg)
assert.Len(t, node.ListPkgs(), 1)

// Span multiple times
for i := 0; i < 2; i++ {
// Wait a little for time shift
// XXX: Change this with `Block` event listening, But we will
// probably need to update block timestamp to nanoseconds first
time.Sleep(time.Second)

msg := vm.MsgCall{
PkgPath: "gno.land/r/dev/foo",
Func: "SpanTime",
}
res, err := testingCallRealm(t, node, msg)
require.NoError(t, err)
require.NoError(t, res.CheckTx.Error)
require.NoError(t, res.DeliverTx.Error)
assert.Equal(t, emitter.NextEvent().Type(), events.EvtTxResult)
}

// Render JSON times list
render, err := testingRenderRealm(t, node, "gno.land/r/dev/foo")
require.NoError(t, err)

// Parse times list
times1 := parseJSONTimesList(t, render)

// Ensure times are correctly expending
for i, t2 := range times1 {
if i == 0 {
continue
}

t1 := times1[i-1]
require.Greater(t, t2.UnixNano(), t1.UnixNano())
}

// Reload the node
err = node.Reload(context.Background())
require.NoError(t, err)
assert.Equal(t, emitter.NextEvent().Type(), events.EvtReload)

// Fetch time list again from render
render, err = testingRenderRealm(t, node, "gno.land/r/dev/foo")
require.NoError(t, err)

times2 := parseJSONTimesList(t, render)

// Times list should be identical
require.Len(t, times2, len(times1))
for i := 0; i < len(times1); i++ {
t1nsec, t2nsec := times1[i].UnixNano(), times2[i].UnixNano()
assert.Equal(t, t1nsec, t2nsec,
"comparing times1[%d](%d) == times2[%d](%d)", i, t1nsec, i, t2nsec)
}
}

func testingRenderRealm(t *testing.T, node *Node, rlmpath string) (string, error) {
t.Helper()

Expand Down Expand Up @@ -296,6 +412,11 @@ func newTestingDevNode(t *testing.T, pkgslist ...PackagePath) (*Node, *mock.Serv

// Call NewDevNode with no package should work
cfg := DefaultNodeConfig(gnoenv.RootDir())

// Minimize time between block to avoid tx being set in the same block
cfg.TMConfig.Consensus.SkipTimeoutCommit = true
cfg.TMConfig.Consensus.TimeoutPropose = time.Millisecond * 100

cfg.PackagesPathList = pkgslist
cfg.Emitter = emitter
cfg.Logger = logger
Expand Down
34 changes: 20 additions & 14 deletions contribs/gnodev/pkg/dev/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/url"
"path/filepath"
"time"

"github.com/gnolang/gno/contribs/gnodev/pkg/address"
"github.com/gnolang/gno/gno.land/pkg/gnoland"
Expand Down Expand Up @@ -119,7 +120,7 @@ func (pm PackagesMap) toList() gnomod.PkgList {
return list
}

func (pm PackagesMap) Load(fee std.Fee) ([]gnoland.TxWithMetadata, error) {
func (pm PackagesMap) Load(fee std.Fee, start time.Time) ([]gnoland.TxWithMetadata, error) {
pkgs := pm.toList()

sorted, err := pkgs.Sort()
Expand All @@ -128,8 +129,8 @@ func (pm PackagesMap) Load(fee std.Fee) ([]gnoland.TxWithMetadata, error) {
}

nonDraft := sorted.GetNonDraftPkgs()
txs := make([]gnoland.TxWithMetadata, 0, len(nonDraft))

metatxs := make([]gnoland.TxWithMetadata, 0, len(nonDraft))
for _, modPkg := range nonDraft {
pkg := pm[modPkg.Dir]
if pkg.Creator.IsZero() {
Expand All @@ -143,22 +144,27 @@ func (pm PackagesMap) Load(fee std.Fee) ([]gnoland.TxWithMetadata, error) {
}

// Create transaction
tx := gnoland.TxWithMetadata{
Tx: std.Tx{
Fee: fee,
Msgs: []std.Msg{
vmm.MsgAddPackage{
Creator: pkg.Creator,
Deposit: pkg.Deposit,
Package: memPkg,
},
tx := std.Tx{
Fee: fee,
Msgs: []std.Msg{
vmm.MsgAddPackage{
Creator: pkg.Creator,
Deposit: pkg.Deposit,
Package: memPkg,
},
},
}

tx.Tx.Signatures = make([]std.Signature, len(tx.Tx.GetSigners()))
txs = append(txs, tx)
tx.Signatures = make([]std.Signature, len(tx.GetSigners()))
metatx := gnoland.TxWithMetadata{
Tx: tx,
Metadata: &gnoland.GnoTxMetadata{
Timestamp: start.Unix(),
},
}

metatxs = append(metatxs, metatx)
}

return txs, nil
return metatxs, nil
}
Loading