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

feat: transactions in the same block have different timestamps #1777

Closed
wants to merge 8 commits into from
Closed
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: 6 additions & 0 deletions examples/gno.land/r/demo/deep/very/deep/render.gno
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package deep

import "time"

func Render(path string) string {
if path == "" {
return "it works!"
} else {
return "hi " + path
}
}

func CurrentTimeUnixNano() int64 {
return time.Now().UnixNano()
}
1 change: 0 additions & 1 deletion gno.land/pkg/gnoclient/client_txs.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTx
// signAndBroadcastTxCommit signs a transaction and broadcasts it, returning the result.
func (c Client) signAndBroadcastTxCommit(tx std.Tx, accountNumber, sequenceNumber uint64) (*ctypes.ResultBroadcastTxCommit, error) {
caller := c.Signer.Info().GetAddress()

if sequenceNumber == 0 || accountNumber == 0 {
account, _, err := c.QueryAccount(caller)
if err != nil {
Expand Down
153 changes: 139 additions & 14 deletions gno.land/pkg/gnoclient/integration_test.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
package gnoclient

import (
"strconv"
"strings"
"sync"
"testing"

"github.com/gnolang/gno/tm2/pkg/std"

"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/gno.land/pkg/integration"
"github.com/gnolang/gno/gnovm/pkg/gnoenv"
rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
core_types "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types"
"github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
"github.com/gnolang/gno/tm2/pkg/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const testChainID string = "tendermint_test"
Copy link
Member

Choose a reason for hiding this comment

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

We should move this into the test, even if we redeclare it

Not a huge fan of having package-level test globals


func TestCallSingle_Integration(t *testing.T) {
// Set up in-memory node
config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir())
node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config)
defer node.Stop()

// Init Signer & RPCClient
signer := newInMemorySigner(t, "tendermint_test")
signer := defaultInMemorySigner(t, "tendermint_test")
rpcClient := rpcclient.NewHTTP(remoteAddr, "/websocket")

// Setup Client
Expand Down Expand Up @@ -65,7 +72,7 @@ func TestCallMultiple_Integration(t *testing.T) {
defer node.Stop()

// Init Signer & RPCClient
signer := newInMemorySigner(t, "tendermint_test")
signer := defaultInMemorySigner(t, "tendermint_test")
Copy link
Member

Choose a reason for hiding this comment

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

I'm laughing that we defined a var but never used it here :)

rpcClient := rpcclient.NewHTTP(remoteAddr, "/websocket")

// Setup Client
Expand Down Expand Up @@ -109,14 +116,126 @@ func TestCallMultiple_Integration(t *testing.T) {
assert.Equal(t, expected, got)
}

func TestMultiTxTimestamp_Integration(t *testing.T) {
config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir())
keybase := keys.NewInMemory()
signerOne := defaultInMemorySigner(t, testChainID)

newAccountMnemonic := "when school roof tomato organ middle bring smile rebuild faith chase fragile increase paddle cool pink model become nation abuse advice sword mimic reduce"
Copy link
Member

Choose a reason for hiding this comment

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

Just curious: do we have a mnemonic generator helper?
I could've sworn we used something for gnokey / gnoland command suite tests

signerTwo := newInMemorySigner(t, testChainID, keybase, newAccountMnemonic, "test2")
config.AddGenesisBalances(
gnoland.Balance{
Address: signerTwo.Address,
Amount: std.Coins{
std.Coin{
Denom: "ugnot",
Amount: 1000000000,
},
},
},
)

node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config)
defer node.Stop()
rpcClient := rpcclient.NewHTTP(remoteAddr, "/websocket")

clientOne := Client{
Signer: signerOne,
RPCClient: rpcClient,
}
clientTwo := Client{
Signer: signerTwo,
RPCClient: rpcClient,
}

type callRes struct {
result *core_types.ResultBroadcastTxCommit
err error
}

maxTries := 5
for i := 1; i <= maxTries; i++ {
resCh := make(chan callRes, 2)
wg := new(sync.WaitGroup)
wg.Add(2)

sendTx := func(client Client) {
Copy link
Member

Choose a reason for hiding this comment

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

Why not just define this outside the loop?

baseCfg := BaseTxCfg{
GasFee: "10000ugnot",
GasWanted: 8000000,
}

// Make Msg configs
msg := MsgCall{
PkgPath: "gno.land/r/demo/deep/very/deep",
FuncName: "CurrentTimeUnixNano",
}

res, err := client.Call(baseCfg, msg)
resCh <- callRes{result: res, err: err}
wg.Done()
}

go sendTx(clientOne)
go sendTx(clientTwo)
wg.Wait()
close(resCh)

results := make([]*core_types.ResultBroadcastTxCommit, 0, 2)
for res := range resCh {
if res.err != nil {
t.Errorf("unexpected error %v", res.err)
}
Comment on lines +186 to +188
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick:

Suggested change
if res.err != nil {
t.Errorf("unexpected error %v", res.err)
}
require.NoError(t, res.err)

results = append(results, res.result)
}

if len(results) != 2 {
t.Errorf("expected 2 results, got %d", len(results))
}
Comment on lines +192 to +194
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick:

Suggested change
if len(results) != 2 {
t.Errorf("expected 2 results, got %d", len(results))
}
require.Len(t, results, 2)


if results[0].Height != results[1].Height {
if i < maxTries {
continue
}
t.Errorf("expected same height, got %d and %d", results[0].Height, results[1].Height)
Comment on lines +197 to +200
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick:

Suggested change
if i < maxTries {
continue
}
t.Errorf("expected same height, got %d and %d", results[0].Height, results[1].Height)
require.Less(t, i, maxTries)

}

extractInt := func(data []byte) int64 {
parts := strings.Split(string(data), " ")
numStr := parts[0][1:]
num, err := strconv.ParseInt(numStr, 10, 64)
if err != nil {
t.Errorf("unable to parse number from string %s", string(data))
}

return num
}
Comment on lines +203 to +212
Copy link
Member

Choose a reason for hiding this comment

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

Why not declare this outside the loop?


time1, time2 := extractInt(results[0].DeliverTx.Data), extractInt(results[1].DeliverTx.Data)
diff := time1 - time2
if diff < 0 {
diff *= -1
}

if diff != 100 {
if i < maxTries {
continue
}
t.Errorf("expected time difference to be 100, got %d", diff)
}

break
}
}

func TestSendSingle_Integration(t *testing.T) {
// Set up in-memory node
config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir())
node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config)
defer node.Stop()

// Init Signer & RPCClient
signer := newInMemorySigner(t, "tendermint_test")
signer := defaultInMemorySigner(t, "tendermint_test")
rpcClient := rpcclient.NewHTTP(remoteAddr, "/websocket")

// Setup Client
Expand Down Expand Up @@ -164,7 +283,7 @@ func TestSendMultiple_Integration(t *testing.T) {
defer node.Stop()

// Init Signer & RPCClient
signer := newInMemorySigner(t, "tendermint_test")
signer := defaultInMemorySigner(t, "tendermint_test")
rpcClient := rpcclient.NewHTTP(remoteAddr, "/websocket")

// Setup Client
Expand Down Expand Up @@ -220,7 +339,7 @@ func TestRunSingle_Integration(t *testing.T) {
defer node.Stop()

// Init Signer & RPCClient
signer := newInMemorySigner(t, "tendermint_test")
signer := defaultInMemorySigner(t, "tendermint_test")
rpcClient := rpcclient.NewHTTP(remoteAddr, "/websocket")

client := Client{
Expand Down Expand Up @@ -278,7 +397,7 @@ func TestRunMultiple_Integration(t *testing.T) {
defer node.Stop()

// Init Signer & RPCClient
signer := newInMemorySigner(t, "tendermint_test")
signer := defaultInMemorySigner(t, "tendermint_test")
rpcClient := rpcclient.NewHTTP(remoteAddr, "/websocket")

client := Client{
Expand Down Expand Up @@ -355,20 +474,26 @@ func main() {
// MsgCall with Send field populated (single/multiple)
// MsgRun with Send field populated (single/multiple)

func newInMemorySigner(t *testing.T, chainid string) *SignerFromKeybase {
func defaultInMemorySigner(t *testing.T, chainID string) *SignerFromKeybase {
t.Helper()

mnemonic := integration.DefaultAccount_Seed
name := integration.DefaultAccount_Name
keybase := keys.NewInMemory()
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: this can be inlined

return newInMemorySigner(t, chainID, keybase, integration.DefaultAccount_Seed, integration.DefaultAccount_Name)
}

func newInMemorySigner(t *testing.T, chainID string, keybase keys.Keybase, mnemonic, name string) *SignerFromKeybase {
t.Helper()

kb := keys.NewInMemory()
_, err := kb.CreateAccount(name, mnemonic, "", "", uint32(0), uint32(0))
require.NoError(t, err)
info, err := keybase.CreateAccount(name, mnemonic, "", "", 0, 0)
if err != nil {
t.Fatalf("unexpected error getting new signer: %v", err)
}

return &SignerFromKeybase{
Keybase: kb, // Stores keys in memory or on disk
Keybase: keybase, // Stores keys in memory or on disk
Account: name, // Account name or bech32 format
Address: info.GetAddress(),
Password: "", // Password for encryption
ChainID: chainid, // Chain ID for transaction signing
ChainID: chainID, // Chain ID for transaction signing
}
}
4 changes: 3 additions & 1 deletion gno.land/pkg/gnoclient/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/gnolang/gno/gno.land/pkg/sdk/vm"
"github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
"github.com/gnolang/gno/tm2/pkg/errors"
"github.com/gnolang/gno/tm2/pkg/std"
Expand All @@ -19,9 +20,10 @@ type Signer interface {
// SignerFromKeybase represents a signer created from a Keybase.
type SignerFromKeybase struct {
Keybase keys.Keybase // Stores keys in memory or on disk
Account string // Account name or bech32 format
Account string // Account name
Password string // Password for encryption
ChainID string // Chain ID for transaction signing
Address crypto.Address
Copy link
Member

Choose a reason for hiding this comment

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

I realize this is for quick lookups, but I honestly don't think we need this, since we can always extract this info from the (keybase, account, password) combo

}

func (s SignerFromKeybase) Validate() error {
Expand Down
15 changes: 15 additions & 0 deletions gno.land/pkg/gnoland/node_inmemory.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ type InMemoryNodeConfig struct {
GenesisMaxVMCycles int64
}

func (cfg *InMemoryNodeConfig) AddGenesisBalances(balances ...Balance) error {
Copy link
Member

Choose a reason for hiding this comment

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

Not sure why this was added?

var (
genesisState GnoGenesisState
ok bool
)

if genesisState, ok = cfg.Genesis.AppState.(GnoGenesisState); !ok {
return fmt.Errorf("unexpected genesis app state type of %t", cfg.Genesis.AppState)
}

genesisState.Balances = append(genesisState.Balances, balances...)
cfg.Genesis.AppState = genesisState
return nil
}

// NewMockedPrivValidator generate a new key
func NewMockedPrivValidator() bft.PrivValidator {
return bft.NewMockPVWithParams(ed25519.GenPrivKey(), false, false)
Expand Down
17 changes: 11 additions & 6 deletions gno.land/pkg/sdk/vm/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) error {
ChainID: ctx.ChainID(),
Height: ctx.BlockHeight(),
Timestamp: ctx.BlockTime().Unix(),
TimestampNano: int64(ctx.BlockTime().Nanosecond()),
Msg: msg,
OrigCaller: creator.Bech32(),
OrigSend: deposit,
Expand Down Expand Up @@ -257,6 +258,7 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) {
ChainID: ctx.ChainID(),
Height: ctx.BlockHeight(),
Timestamp: ctx.BlockTime().Unix(),
TimestampNano: int64(ctx.BlockTime().Nanosecond()),
Msg: msg,
OrigCaller: caller.Bech32(),
OrigSend: send,
Expand Down Expand Up @@ -328,6 +330,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) {
ChainID: ctx.ChainID(),
Height: ctx.BlockHeight(),
Timestamp: ctx.BlockTime().Unix(),
TimestampNano: int64(ctx.BlockTime().Nanosecond()),
Msg: msg,
OrigCaller: caller.Bech32(),
OrigSend: send,
Expand Down Expand Up @@ -457,9 +460,10 @@ func (vm *VMKeeper) QueryEval(ctx sdk.Context, pkgPath string, expr string) (res
}
// Construct new machine.
msgCtx := stdlibs.ExecContext{
ChainID: ctx.ChainID(),
Height: ctx.BlockHeight(),
Timestamp: ctx.BlockTime().Unix(),
ChainID: ctx.ChainID(),
Height: ctx.BlockHeight(),
Timestamp: ctx.BlockTime().Unix(),
TimestampNano: int64(ctx.BlockTime().Nanosecond()),
// Msg: msg,
// OrigCaller: caller,
// OrigSend: send,
Expand Down Expand Up @@ -517,9 +521,10 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string
}
// Construct new machine.
msgCtx := stdlibs.ExecContext{
ChainID: ctx.ChainID(),
Height: ctx.BlockHeight(),
Timestamp: ctx.BlockTime().Unix(),
ChainID: ctx.ChainID(),
Height: ctx.BlockHeight(),
Timestamp: ctx.BlockTime().Unix(),
TimestampNano: int64(ctx.BlockTime().Nanosecond()),
// Msg: msg,
// OrigCaller: caller,
// OrigSend: jsend,
Expand Down
4 changes: 2 additions & 2 deletions gnovm/stdlibs/std/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
type ExecContext struct {
ChainID string
Height int64
Timestamp int64 // seconds
TimestampNano int64 // nanoseconds, only used for testing.
Timestamp int64
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to keep Timestamp if we have the nano version?
Can't we just make the Timestamp be the nano version?

TimestampNano int64
Msg sdk.Msg
OrigCaller crypto.Bech32Address
OrigPkgAddr crypto.Bech32Address
Expand Down
1 change: 1 addition & 0 deletions gnovm/tests/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri
ctx.Timestamp += 1
ctx.TimestampNano -= int64(time.Second)
}

m.Context = ctx
},
)
Expand Down
Loading
Loading