Skip to content

Commit 67a5e38

Browse files
authored
fix: simulate and compute total gas usage before token distribution (#89)
* fix: simulate and compute total gas usage before distribution * adapt methods to crossing realm logic * remove println * pass max gas limit while simulating * augment lint timeout * fix lint problem * compute max gas amount on last block * take gas price from pipeline
1 parent 8424f14 commit 67a5e38

File tree

16 files changed

+477
-178
lines changed

16 files changed

+477
-178
lines changed

.github/workflows/lint.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ jobs:
2020
uses: golangci/golangci-lint-action@v6
2121
with:
2222
version: v1.64
23+
args: --timeout=5m

go.mod

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,66 @@
11
module github.com/gnolang/supernova
22

3-
go 1.23
3+
go 1.23.0
4+
5+
toolchain go1.24.1
46

57
require (
6-
github.com/gnolang/gno v0.0.0-20250129165357-b392287f0d2c
8+
github.com/gnolang/gno v0.0.0-20250826105341-fba926958b6b
79
github.com/peterbourgon/ff/v3 v3.4.0
810
github.com/schollz/progressbar/v3 v3.18.0
911
github.com/stretchr/testify v1.10.0
1012
)
1113

1214
require (
1315
dario.cat/mergo v1.0.1 // indirect
16+
github.com/DataDog/zstd v1.4.5 // indirect
17+
github.com/beorn7/perks v1.0.1 // indirect
1418
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
1519
github.com/btcsuite/btcd/btcutil v1.1.6 // indirect
1620
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
21+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
1722
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
23+
github.com/cockroachdb/errors v1.11.3 // indirect
24+
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
25+
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
26+
github.com/cockroachdb/pebble v1.1.5 // indirect
27+
github.com/cockroachdb/redact v1.1.5 // indirect
28+
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
1829
github.com/davecgh/go-spew v1.1.1 // indirect
1930
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
2031
github.com/fsnotify/fsnotify v1.5.4 // indirect
32+
github.com/getsentry/sentry-go v0.27.0 // indirect
2133
github.com/go-logr/logr v1.4.2 // indirect
2234
github.com/go-logr/stdr v1.2.2 // indirect
35+
github.com/gofrs/flock v0.12.1 // indirect
36+
github.com/gogo/protobuf v1.3.2 // indirect
37+
github.com/golang/protobuf v1.5.4 // indirect
2338
github.com/golang/snappy v0.0.4 // indirect
2439
github.com/google/uuid v1.6.0 // indirect
2540
github.com/gorilla/websocket v1.5.3 // indirect
2641
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
42+
github.com/klauspost/compress v1.16.0 // indirect
43+
github.com/kr/pretty v0.3.1 // indirect
44+
github.com/kr/text v0.2.0 // indirect
2745
github.com/lib/pq v1.10.9 // indirect
2846
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
47+
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
2948
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
3049
github.com/onsi/gomega v1.31.1 // indirect
3150
github.com/pelletier/go-toml v1.9.5 // indirect
51+
github.com/pkg/errors v0.9.1 // indirect
3252
github.com/pmezard/go-difflib v1.0.0 // indirect
53+
github.com/prometheus/client_golang v1.15.0 // indirect
54+
github.com/prometheus/client_model v0.3.0 // indirect
55+
github.com/prometheus/common v0.42.0 // indirect
56+
github.com/prometheus/procfs v0.9.0 // indirect
3357
github.com/rivo/uniseg v0.4.7 // indirect
58+
github.com/rogpeppe/go-internal v1.13.1 // indirect
3459
github.com/rs/cors v1.11.1 // indirect
3560
github.com/rs/xid v1.6.0 // indirect
3661
github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect
3762
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
63+
github.com/valyala/bytebufferpool v1.0.0 // indirect
3864
go.etcd.io/bbolt v1.3.11 // indirect
3965
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
4066
go.opentelemetry.io/otel v1.34.0 // indirect
@@ -46,14 +72,15 @@ require (
4672
go.opentelemetry.io/otel/trace v1.34.0 // indirect
4773
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
4874
go.uber.org/multierr v1.11.0 // indirect
49-
golang.org/x/crypto v0.32.0 // indirect
75+
golang.org/x/crypto v0.40.0 // indirect
5076
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
51-
golang.org/x/mod v0.22.0 // indirect
52-
golang.org/x/net v0.34.0 // indirect
53-
golang.org/x/sync v0.10.0 // indirect
54-
golang.org/x/sys v0.29.0 // indirect
55-
golang.org/x/term v0.28.0 // indirect
56-
golang.org/x/text v0.21.0 // indirect
77+
golang.org/x/mod v0.26.0 // indirect
78+
golang.org/x/net v0.42.0 // indirect
79+
golang.org/x/sync v0.16.0 // indirect
80+
golang.org/x/sys v0.34.0 // indirect
81+
golang.org/x/term v0.33.0 // indirect
82+
golang.org/x/text v0.28.0 // indirect
83+
golang.org/x/tools v0.35.0 // indirect
5784
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
5885
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
5986
google.golang.org/grpc v1.69.4 // indirect

go.sum

Lines changed: 86 additions & 16 deletions
Large diffs are not rendered by default.

internal/client/client.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import (
1212
"github.com/gnolang/supernova/internal/common"
1313
)
1414

15-
const simulatePath = ".app/simulate"
15+
const (
16+
simulatePath = ".app/simulate"
17+
gaspricePath = "auth/gasprice"
18+
)
1619

1720
type Client struct {
1821
conn *client.RPCClient
@@ -170,3 +173,20 @@ func (h *Client) EstimateGas(tx *std.Tx) (int64, error) {
170173
// for executing the transaction
171174
return deliverTx.GasUsed, nil
172175
}
176+
177+
func (h *Client) FetchGasPrice() (std.GasPrice, error) {
178+
// Perform auth/gasprice
179+
gp := std.GasPrice{}
180+
181+
qres, err := h.conn.ABCIQuery(gaspricePath, []byte{})
182+
if err != nil {
183+
return gp, err
184+
}
185+
186+
err = amino.UnmarshalJSON(qres.Response.Data, &gp)
187+
if err != nil {
188+
return gp, err
189+
}
190+
191+
return gp, nil
192+
}

internal/distributor/distributor.go

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Client interface {
2020
GetAccount(address string) (*gnoland.GnoAccount, error)
2121
BroadcastTransaction(tx *std.Tx) error
2222
EstimateGas(tx *std.Tx) (int64, error)
23+
FetchGasPrice() (std.GasPrice, error)
2324
}
2425

2526
// Distributor is the process
@@ -42,41 +43,20 @@ func NewDistributor(
4243
func (d *Distributor) Distribute(
4344
distributor crypto.PrivKey,
4445
accounts []crypto.Address,
45-
transactions uint64,
4646
chainID string,
47+
gasPrice std.GasPrice,
48+
calculatedRuntimeCost std.Coin,
4749
) ([]std.Account, error) {
4850
fmt.Printf("\n💸 Starting Fund Distribution 💸\n\n")
4951

50-
// Calculate the base fees
51-
subAccountCost := calculateRuntimeCosts(int64(transactions))
5252
fmt.Printf(
5353
"Calculated sub-account cost as %d %s\n",
54-
subAccountCost.Amount,
55-
subAccountCost.Denom,
54+
calculatedRuntimeCost.Amount,
55+
calculatedRuntimeCost.Denom,
5656
)
5757

5858
// Fund the accounts
59-
return d.fundAccounts(distributor, accounts, subAccountCost, chainID)
60-
}
61-
62-
// calculateRuntimeCosts calculates the amount of funds
63-
// each account needs to have in order to participate in the
64-
// stress test run
65-
func calculateRuntimeCosts(totalTx int64) std.Coin {
66-
// Cost of a single run transaction for the sub-account
67-
// NOTE: Since there is no gas estimation support yet, this value
68-
// is fixed, but it will change in the future once pricing estimations
69-
// are added
70-
baseTxCost := common.CalculateFeeInRatio(1_000_000, common.DefaultGasPrice)
71-
72-
// Each account should have enough funds
73-
// to execute the entire run
74-
subAccountCost := std.Coin{
75-
Denom: common.Denomination,
76-
Amount: totalTx * baseTxCost.GasFee.Amount,
77-
}
78-
79-
return subAccountCost
59+
return d.fundAccounts(distributor, accounts, calculatedRuntimeCost, chainID, gasPrice)
8060
}
8161

8262
// fundAccounts attempts to fund accounts that have missing funds,
@@ -86,6 +66,7 @@ func (d *Distributor) fundAccounts(
8666
accounts []crypto.Address,
8767
singleRunCost std.Coin,
8868
chainID string,
69+
gasPrice std.GasPrice,
8970
) ([]std.Account, error) {
9071
type shortAccount struct {
9172
missingFunds std.Coin
@@ -150,7 +131,7 @@ func (d *Distributor) fundAccounts(
150131
var (
151132
distributorBalance = distributor.Coins
152133
fundableIndex = 0
153-
defaultFee = common.CalculateFeeInRatio(100_000, common.DefaultGasPrice)
134+
defaultFee = common.CalculateFeeInRatio(100_000, gasPrice)
154135
)
155136

156137
for _, account := range shortAccounts {

internal/distributor/distributor_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ func TestDistributor_Distribute(t *testing.T) {
1717

1818
var (
1919
numTx = uint64(1000)
20-
singleCost = calculateRuntimeCosts(int64(numTx))
20+
singleCost = std.Coin{
21+
Denom: common.Denomination,
22+
Amount: int64(numTx) * 100_00,
23+
}
2124
)
2225

2326
getAccount := func(address string, accounts []crypto.PrivKey) crypto.PrivKey {
@@ -66,7 +69,7 @@ func TestDistributor_Distribute(t *testing.T) {
6669
addresses = append(addresses, account.PubKey().Address())
6770
}
6871

69-
readyAccounts, err := d.Distribute(accounts[0], addresses, numTx, "dummy")
72+
readyAccounts, err := d.Distribute(accounts[0], addresses, "dummy", common.DefaultGasPrice, singleCost)
7073
if err != nil {
7174
t.Fatalf("unable to distribute funds, %v", err)
7275
}
@@ -122,7 +125,7 @@ func TestDistributor_Distribute(t *testing.T) {
122125
addresses = append(addresses, account.PubKey().Address())
123126
}
124127

125-
readyAccounts, err := d.Distribute(accounts[0], addresses, numTx, "dummy")
128+
readyAccounts, err := d.Distribute(accounts[0], addresses, "dummy", common.DefaultGasPrice, singleCost)
126129

127130
assert.Nil(t, readyAccounts)
128131
assert.ErrorIs(t, err, errInsufficientFunds)
@@ -192,7 +195,7 @@ func TestDistributor_Distribute(t *testing.T) {
192195
addresses = append(addresses, account.PubKey().Address())
193196
}
194197

195-
readyAccounts, err := d.Distribute(accounts[0], addresses, numTx, "dummy")
198+
readyAccounts, err := d.Distribute(accounts[0], addresses, "dummy", common.DefaultGasPrice, singleCost)
196199
if err != nil {
197200
t.Fatalf("unable to distribute funds, %v", err)
198201
}

internal/distributor/mock_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ type (
99
broadcastTransactionDelegate func(*std.Tx) error
1010
getAccountDelegate func(string) (*gnoland.GnoAccount, error)
1111
estimateGasDelegate func(*std.Tx) (int64, error)
12+
fetchGasPriceDelegate func() (std.GasPrice, error)
1213
)
1314

1415
type mockClient struct {
1516
broadcastTransactionFn broadcastTransactionDelegate
1617
getAccountFn getAccountDelegate
1718
estimateGasFn estimateGasDelegate
19+
fetchGasPriceFn fetchGasPriceDelegate
1820
}
1921

2022
func (m *mockClient) BroadcastTransaction(tx *std.Tx) error {
@@ -40,3 +42,11 @@ func (m *mockClient) EstimateGas(tx *std.Tx) (int64, error) {
4042

4143
return 0, nil
4244
}
45+
46+
func (m *mockClient) FetchGasPrice() (std.GasPrice, error) {
47+
if m.fetchGasPriceFn != nil {
48+
return m.fetchGasPriceFn()
49+
}
50+
51+
return std.GasPrice{}, nil
52+
}

internal/pipeline.go

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/gnolang/gno/tm2/pkg/crypto"
88
"github.com/gnolang/gno/tm2/pkg/crypto/bip39"
9+
"github.com/gnolang/gno/tm2/pkg/std"
910
"github.com/gnolang/supernova/internal/batcher"
1011
"github.com/gnolang/supernova/internal/client"
1112
"github.com/gnolang/supernova/internal/collector"
@@ -65,14 +66,32 @@ func (p *Pipeline) Execute() error {
6566
// Initialize the accounts for the runtime
6667
accounts := p.initializeAccounts()
6768

69+
gasPrice, err := p.cli.FetchGasPrice()
70+
if err != nil {
71+
return err
72+
}
73+
74+
lastBlock, err := p.cli.GetLatestBlockHeight()
75+
if err != nil {
76+
return fmt.Errorf("unable to get last block, %w", err)
77+
}
78+
79+
maxGas, err := p.cli.GetBlockGasLimit(lastBlock)
80+
if err != nil {
81+
return fmt.Errorf("unable to get block gas limit, %w", err)
82+
}
6883
// Predeploy any pending transactions
69-
if err := prepareRuntime(
84+
estimatedGas, err := prepareRuntime(
7085
mode,
7186
accounts[0],
7287
p.cfg.ChainID,
7388
p.cli,
7489
txRuntime,
75-
); err != nil {
90+
maxGas,
91+
gasPrice,
92+
p.cfg.Transactions,
93+
)
94+
if err != nil {
7695
return err
7796
}
7897

@@ -86,8 +105,9 @@ func (p *Pipeline) Execute() error {
86105
runAccounts, err := distributor.NewDistributor(p.cli).Distribute(
87106
accounts[0],
88107
addresses,
89-
p.cfg.Transactions,
90108
p.cfg.ChainID,
109+
gasPrice,
110+
estimatedGas,
91111
)
92112
if err != nil {
93113
return fmt.Errorf("unable to distribute funds, %w", err)
@@ -109,6 +129,8 @@ func (p *Pipeline) Execute() error {
109129
runKeys,
110130
runAccounts,
111131
p.cfg.Transactions,
132+
maxGas,
133+
gasPrice,
112134
p.cfg.ChainID,
113135
p.cli.EstimateGas,
114136
)
@@ -192,42 +214,48 @@ func prepareRuntime(
192214
chainID string,
193215
cli pipelineClient,
194216
txRuntime runtime.Runtime,
195-
) error {
196-
if mode != runtime.RealmCall {
197-
return nil
198-
}
199-
200-
fmt.Printf("\n✨ Starting Predeployment Procedure ✨\n\n")
201-
217+
currentMaxGas int64,
218+
gasPrice std.GasPrice,
219+
transactions uint64,
220+
) (std.Coin, error) {
202221
// Get the deployer account
203222
deployer, err := cli.GetAccount(deployerKey.PubKey().Address().String())
204223
if err != nil {
205-
return fmt.Errorf("unable to fetch deployer account, %w", err)
224+
return std.Coin{}, fmt.Errorf("unable to fetch deployer account, %w", err)
206225
}
207226

227+
signCB := runtime.SignTransactionsCb(chainID, deployer, deployerKey)
228+
229+
if mode != runtime.RealmCall {
230+
return txRuntime.CalculateRuntimeCosts(deployer, cli.EstimateGas, signCB, currentMaxGas, gasPrice, transactions)
231+
}
232+
233+
fmt.Printf("\n✨ Starting Predeployment Procedure ✨\n\n")
234+
208235
// Get the predeploy transactions
209236
predeployTxs, err := txRuntime.Initialize(
210237
deployer,
211-
deployerKey,
212-
chainID,
238+
signCB,
213239
cli.EstimateGas,
240+
currentMaxGas,
241+
gasPrice,
214242
)
215243
if err != nil {
216-
return fmt.Errorf("unable to initialize runtime, %w", err)
244+
return std.Coin{}, fmt.Errorf("unable to initialize runtime, %w", err)
217245
}
218246

219247
bar := progressbar.Default(int64(len(predeployTxs)), "predeployed txs")
220248

221249
// Execute the predeploy transactions
222250
for _, tx := range predeployTxs {
223251
if err := cli.BroadcastTransaction(tx); err != nil {
224-
return fmt.Errorf("unable to broadcast predeploy tx, %w", err)
252+
return std.Coin{}, fmt.Errorf("unable to broadcast predeploy tx, %w", err)
225253
}
226254

227255
_ = bar.Add(1) //nolint:errcheck // No need to check
228256
}
229257

230258
fmt.Printf("✅ Successfully predeployed %d transactions\n", len(predeployTxs))
231259

232-
return nil
260+
return txRuntime.CalculateRuntimeCosts(deployer, cli.EstimateGas, signCB, currentMaxGas, gasPrice, transactions)
233261
}

0 commit comments

Comments
 (0)