-
Notifications
You must be signed in to change notification settings - Fork 399
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
Changes from all commits
b4d8e07
a2be089
d340f72
3833453
d04795c
ae13785
3f6f1e7
c29c858
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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() | ||
} |
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" | ||||||||||||
|
||||||||||||
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 | ||||||||||||
|
@@ -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") | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm laughing that we defined a |
||||||||||||
rpcClient := rpcclient.NewHTTP(remoteAddr, "/websocket") | ||||||||||||
|
||||||||||||
// Setup Client | ||||||||||||
|
@@ -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" | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious: do we have a mnemonic generator helper? |
||||||||||||
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) { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick:
Suggested change
|
||||||||||||
results = append(results, res.result) | ||||||||||||
} | ||||||||||||
|
||||||||||||
if len(results) != 2 { | ||||||||||||
t.Errorf("expected 2 results, got %d", len(results)) | ||||||||||||
} | ||||||||||||
Comment on lines
+192
to
+194
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick:
Suggested change
|
||||||||||||
|
||||||||||||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick:
Suggested change
|
||||||||||||
} | ||||||||||||
|
||||||||||||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||||||||
|
@@ -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 | ||||||||||||
|
@@ -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{ | ||||||||||||
|
@@ -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{ | ||||||||||||
|
@@ -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() | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||||||||
} | ||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
} | ||
|
||
func (s SignerFromKeybase) Validate() error { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,21 @@ type InMemoryNodeConfig struct { | |
GenesisMaxVMCycles int64 | ||
} | ||
|
||
func (cfg *InMemoryNodeConfig) AddGenesisBalances(balances ...Balance) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,8 +9,8 @@ import ( | |
type ExecContext struct { | ||
ChainID string | ||
Height int64 | ||
Timestamp int64 // seconds | ||
TimestampNano int64 // nanoseconds, only used for testing. | ||
Timestamp int64 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to keep |
||
TimestampNano int64 | ||
Msg sdk.Msg | ||
OrigCaller crypto.Bech32Address | ||
OrigPkgAddr crypto.Bech32Address | ||
|
There was a problem hiding this comment.
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