Skip to content

Commit 9ffc382

Browse files
committed
evm-gateway-demo: clean port from chase + SDK baebbe88 + PR #185 review items
SDK upgrade (relative to chase): - Bump fabric-x-sdk to baebbe88 (persistent streams + native ArMA Send→Recv ordering) - Add context.Context arg to NewSubmitter calls (breaking API change) - Drop custom ArMASubmitter: SDK now handles ordering correctly - Switch perf test to NewFabricXTestHarnessWithFactory (fabric-x staging) - Add FABX_CONFIG_PATH env support (Ansible sets per-replica path) - Add PERF_PROCESSING_WORKERS / PERF_SUBMITTING_WORKERS env vars Ported from PR #185: - Dockerfile: mkdir /data && chmod 777 /data (busybox has no /data) - endorser/versioned_db_wrapper.go: NewSnapshot(0) resolves to latest committed block (was reading genesis state for "latest") - gateway/api/models.go: gasPrice nil fix for EIP-1559 receipts - integration/test_helpers.go: waitUntilSynced in NewFabricXTestHarnessWithFactory (60s timeout) so tests don't start before gateway is synced - integration/perf/replay_json_dataset_test.go: require.NoError for harness setup - endorser/testimpl/balance_priming_statedb.go: SetSender now actually sets senderAddr and balanceSlot; GetState only intercepts the specific balance slot (was intercepting ALL contract storage and returning primeValue, which made _paused/_blacklisted/etc. read as non-zero and revert every transfer) - endorser/testimpl/balance_priming_executor.go: recover real sender from tx and pass to SetSender (was passing zero address); convert vm.ErrExecutionReverted into ExecutionResult{Status:201} so reverts commit and the test can detect them Code review items addressed: - replay_json_dataset_test.go: switch polling from TransactionByHash to TransactionReceipt; check receipt.Status (1=success, 0=EVM revert) for correct failure accounting — this also exposed the priming bug above - gateway/app/app.go: default SyncTimeout 5m → 60s - gateway/storage/store.go + gateway/core/chain.go: parameterize TruncateBlocks frequency via Store.BlockTruncationInterval (was hardcoded 1000) Tooling: - scripts/run-demo.sh: add --submitting-workers, --processing-workers flags; quiet-mode uses ssh -T instead of -tt (no PTY CR/LF corruption) and surfaces section headers, replica progress, and demo result lines Docs: - integration/perf/USDC_deployment.md: trim stale troubleshooting entries and duplicate command examples; collapse output-format section to essentials
1 parent 7fb7273 commit 9ffc382

14 files changed

Lines changed: 168 additions & 254 deletions

File tree

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ RUN CGO_ENABLED=0 go build -trimpath -o /fxevm ./cmd/fxevm
99
FROM docker.io/library/busybox
1010

1111
COPY --from=build /fxevm /usr/local/bin/fxevm
12+
RUN mkdir /data && chmod 777 /data
1213
EXPOSE 8545
1314
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=30s \
1415
CMD ["fxevm", "healthcheck"]

endorser/testimpl/balance_priming_executor.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ import (
1010
"context"
1111
"encoding/json"
1212
"errors"
13+
"fmt"
1314

1415
"github.com/ethereum/go-ethereum/common"
1516
"github.com/ethereum/go-ethereum/core/types"
17+
"github.com/ethereum/go-ethereum/core/vm"
18+
fxcommon "github.com/hyperledger/fabric-x-evm/common"
1619
"github.com/hyperledger/fabric-x-evm/endorser"
1720
"github.com/hyperledger/fabric-x-evm/utils"
1821
"github.com/hyperledger/fabric-x-sdk/endorsement"
@@ -96,24 +99,42 @@ func NewBalancePrimingExecutor(
9699

97100
// Execute runs a state-changing transaction with SenderAware notification.
98101
func (e *BalancePrimingExecutor) Execute(tx *types.Transaction) (endorsement.ExecutionResult, error) {
99-
// Extract the sender to notify SenderAware wrappers
100-
// This replicates the logic from the original Executor.Send
101-
102-
// Notify NonceAware wrappers of the expected nonce for this transaction
102+
// Notify NonceAware wrappers of the expected nonce for this transaction.
103103
if na, ok := e.state.(NonceAware); ok {
104104
na.SetExpectedNonce(tx.Nonce())
105105
}
106106

107-
// Notify SenderAware wrappers of the transaction sender
107+
// Notify SenderAware wrappers of the actual transaction sender so that
108+
// balance priming targets the correct storage slot.
109+
// e.Executor.ChainCfg is the same chain config used by Executor.Send, so
110+
// the recovered address will match the one the EVM uses.
108111
if sa, ok := e.state.(SenderAware); ok {
109-
sa.SetSender(common.Address{})
112+
signer := types.LatestSignerForChainID(e.Executor.ChainCfg.ChainID)
113+
from, err := types.Sender(signer, tx)
114+
if err != nil {
115+
return endorsement.ExecutionResult{}, fmt.Errorf("recover sender: %w", err)
116+
}
117+
sa.SetSender(from)
110118
}
111119

112120
// Execute the transaction using the base Executor
113121
ret, err := e.Executor.Send(tx)
114-
if err != nil {
122+
if err != nil && !errors.Is(err, vm.ErrExecutionReverted) {
115123
return endorsement.ExecutionResult{}, err
116124
}
125+
if errors.Is(err, vm.ErrExecutionReverted) {
126+
event, mErr := fxcommon.MarshalRevert(ret, "", tx.Hash().Hex())
127+
if mErr != nil {
128+
return endorsement.ExecutionResult{}, fmt.Errorf("marshal revert event: %w", mErr)
129+
}
130+
return endorsement.ExecutionResult{
131+
RWS: e.state.Result(),
132+
Event: event,
133+
Status: 201,
134+
Message: err.Error(),
135+
Payload: ret,
136+
}, nil
137+
}
117138

118139
// Marshal logs if any
119140
var logs []byte

endorser/testimpl/balance_priming_statedb.go

Lines changed: 18 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ SPDX-License-Identifier: LGPL-3.0-or-later
77
package testimpl
88

99
import (
10-
"fmt"
1110
"math/big"
1211

1312
"github.com/ethereum/go-ethereum/common"
@@ -42,56 +41,30 @@ func NewBalancePrimingWrapper(stateDB endorser.ExtendedStateDB, contractAddr com
4241
}
4342
}
4443

45-
// SetSender sets the sender address and calculates the balance slot.
44+
// SetSender sets the sender address and calculates the balance slot for priming.
45+
// Only the sender's balance slot will be primed; all other contract storage is
46+
// passed through unchanged so that fields like _paused, _blacklisted, etc. are
47+
// not accidentally set to a non-zero value.
4648
func (w *BalancePrimingWrapper) SetSender(sender common.Address) {
4749
w.enabled = true
48-
49-
if false {
50-
fmt.Printf("[BalancePriming] SetSender called: sender=%s, balanceSlot=%s, contractAddr=%s\n",
51-
sender.Hex(), w.balanceSlot.Hex(), w.contractAddr.Hex())
52-
}
50+
w.senderAddr = sender
51+
w.balanceSlot = GetERC20BalanceSlot(sender, w.mappingPosition)
5352
}
5453

55-
// GetState intercepts storage reads and primes the balance slot if needed.
54+
// GetState intercepts storage reads and primes the sender's balance slot if needed.
55+
// Only the slot computed from the sender address (set via SetSender) is intercepted;
56+
// all other slots are passed through unchanged. This avoids incorrectly priming
57+
// boolean/address slots such as _paused or _blacklisted.
5658
func (w *BalancePrimingWrapper) GetState(addr common.Address, slot common.Hash) common.Hash {
57-
// Check if this is a read of our target balance slot
58-
if w.enabled && addr == w.contractAddr {
59-
if false {
60-
fmt.Printf("[BalancePriming] GetState intercepted: addr=%s, slot=%s (matches target)\n",
61-
addr.Hex(), slot.Hex())
62-
}
63-
64-
// Get the current value
65-
currentValue := w.ExtendedStateDB.GetState(addr, slot)
66-
67-
if false {
68-
fmt.Printf("[BalancePriming] Current value: %s\n", currentValue.Hex())
69-
}
70-
71-
// If it's zero, prime it with a high value
72-
if currentValue == (common.Hash{}) {
73-
if false {
74-
fmt.Printf("[BalancePriming] *** PRIMING BALANCE *** sender=%s, slot=%s, value=%s\n",
75-
w.senderAddr.Hex(), slot.Hex(), primeValue.String())
76-
}
77-
78-
// Intentionally not calling SetState here. Writing the primed value to the
79-
// StateDB would include it in the transaction's write set and commit a fake
80-
// balance to the ledger, affecting future transactions. Returning it only
81-
// from GetState keeps the priming invisible to the ledger while still
82-
// allowing the EVM execution to proceed with a non-zero balance.
83-
84-
// Return the primed value
85-
return common.BytesToHash(primeValue.Bytes())
86-
} else {
87-
if false {
88-
fmt.Printf("[BalancePriming] Balance already set, not priming\n")
89-
}
90-
}
59+
result := w.ExtendedStateDB.GetState(addr, slot)
60+
// Only intercept the specific balance slot for the known sender.
61+
if w.enabled && addr == w.contractAddr && slot == w.balanceSlot && result == (common.Hash{}) {
62+
// Return a synthetic high balance. The EVM will write the decremented balance
63+
// (primeValue - amount) via SetState, which is fine. Not writing the raw
64+
// primeValue keeps the ledger clean.
65+
return common.BytesToHash(primeValue.Bytes())
9166
}
92-
93-
// Otherwise, just pass through to the underlying StateDB
94-
return w.ExtendedStateDB.GetState(addr, slot)
67+
return result
9568
}
9669

9770
// SetExpectedNonce stores the nonce the current transaction expects.

endorser/versioned_db_wrapper.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,15 @@ func NewVersionedDBWrapper(db *state.VersionedDB) *VersionedDBWrapper {
3030

3131
// NewSnapshot creates a new snapshot of the state at the specified block number.
3232
// It returns a VersionedDBSnapshot that will use this block number for all Get operations,
33-
// providing snapshot isolation.
33+
// providing snapshot isolation. If blockNumber is 0 it resolves to the latest committed block.
3434
func (w *VersionedDBWrapper) NewSnapshot(blockNumber uint64) (ReadStore, error) {
35+
if blockNumber == 0 {
36+
latest, err := w.db.BlockNumber(context.Background())
37+
if err != nil {
38+
return nil, err
39+
}
40+
blockNumber = latest
41+
}
3542
return &VersionedDBSnapshot{
3643
db: w.db,
3744
blockNumber: blockNumber,

gateway/api/models.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ func (r *RPCTransaction) MarshalJSON() ([]byte, error) {
116116
// Remove internal go-ethereum fields that shouldn't be exposed
117117
delete(m, "ignore")
118118

119+
// gasPrice must always be a hex string; EIP-1559 txs leave it null in go-ethereum's marshaler
120+
if m["gasPrice"] == nil {
121+
m["gasPrice"] = (*hexutil.Big)(big.NewInt(0))
122+
}
123+
119124
// Add block metadata and sender - these override any fields from the transaction
120125
m["from"] = r.From
121126
m["blockHash"] = r.BlockHash

gateway/app/app.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@ func buildApp(cfg config.Config, gwSigner sdk.Signer, logger sdk.Logger, endorse
104104
var submitter core.Submitter
105105
switch cfg.Network.Protocol {
106106
case "fabric":
107-
submitter, err = nfab.NewSubmitter(orderers, gwSigner, 0, logger)
107+
submitter, err = nfab.NewSubmitter(context.Background(), orderers, gwSigner, 0, logger)
108108
case "fabric-x", "":
109-
submitter, err = nfabx.NewSubmitter(orderers, gwSigner, 0, logger)
109+
submitter, err = nfabx.NewSubmitter(context.Background(), orderers, gwSigner, 0, logger)
110110
default:
111111
return nil, fmt.Errorf("unsupported protocol: %q", cfg.Network.Protocol)
112112
}
@@ -198,7 +198,7 @@ func (a *App) Run(ctx context.Context) error {
198198
// Wait for initial sync before serving traffic
199199
syncTimeout := a.cfg.Gateway.SyncTimeout
200200
if syncTimeout == 0 {
201-
syncTimeout = 5 * time.Minute
201+
syncTimeout = 60 * time.Second
202202
}
203203
for i, sync := range a.endorserSyncs {
204204
if err := waitUntilSynced(gctx, sync, syncTimeout); err != nil {

gateway/core/chain.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func (c *Chain) Handle(ctx context.Context, b blocks.Block) error {
118118
return err
119119
}
120120

121-
if c.Store.BlockRetention > 0 && ebl.BlockNumber%1000 == 0 {
121+
if c.Store.BlockRetention > 0 && c.Store.BlockTruncationInterval > 0 && ebl.BlockNumber%c.Store.BlockTruncationInterval == 0 {
122122
if err := c.Store.TruncateBlocks(ctx, c.Store.BlockRetention); err != nil {
123123
logger.Warnf("block DB truncation failed at block %d: %v", ebl.BlockNumber, err)
124124
}

gateway/storage/store.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,24 @@ import (
2121
var ddl string
2222

2323
type Store struct {
24-
queries *Queries
25-
DB *sql.DB
26-
CachedBlockNumber atomic.Uint64 // cached block number for fast reads
27-
BlockRetention int // number of recent blocks to keep; 0 disables truncation
24+
queries *Queries
25+
DB *sql.DB
26+
CachedBlockNumber atomic.Uint64 // cached block number for fast reads
27+
BlockRetention int // number of recent blocks to keep; 0 disables truncation
28+
BlockTruncationInterval uint64 // run truncation every N blocks; 0 disables truncation
2829
}
2930

30-
const DefaultBlockRetention = 10000
31+
const (
32+
DefaultBlockRetention = 10000
33+
DefaultBlockTruncationInterval = 1000
34+
)
3135

3236
func NewStore(db *sql.DB) *Store {
3337
return &Store{
34-
queries: New(db),
35-
DB: db,
36-
BlockRetention: DefaultBlockRetention,
38+
queries: New(db),
39+
DB: db,
40+
BlockRetention: DefaultBlockRetention,
41+
BlockTruncationInterval: DefaultBlockTruncationInterval,
3742
}
3843
}
3944

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ require (
1717
github.com/hyperledger/fabric-lib-go v1.1.3
1818
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7
1919
github.com/hyperledger/fabric-x-common v0.1.1-0.20260219094834-26c5a49ed548
20-
github.com/hyperledger/fabric-x-sdk v0.0.0-20260521090010-c60d735b6023
20+
github.com/hyperledger/fabric-x-sdk v0.0.0-20260529170509-baebbe88ff86
2121
github.com/spf13/cobra v1.10.2
2222
github.com/stretchr/testify v1.11.1
2323
golang.org/x/crypto v0.50.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -982,8 +982,8 @@ github.com/hyperledger/fabric-x-committer v0.1.9 h1:3lgl1g3FkONogjJraRzhrD7gr89X
982982
github.com/hyperledger/fabric-x-committer v0.1.9/go.mod h1:MjD6yyRfnCk3QjmwbKySoApvQerfnR++f0exQyFcZNw=
983983
github.com/hyperledger/fabric-x-common v0.1.1-0.20260219094834-26c5a49ed548 h1:KTmH/ZtM43z39lcLxGm7G3I5iC7J7iZSVHqbCFcUUNo=
984984
github.com/hyperledger/fabric-x-common v0.1.1-0.20260219094834-26c5a49ed548/go.mod h1:+VPYRRCGAZo7+rlT55yK3aRmUbRJwQGlWg7lz0SLdMY=
985-
github.com/hyperledger/fabric-x-sdk v0.0.0-20260521090010-c60d735b6023 h1:TzPwU9TF6QdolKRcmWp38NEDG7EBB3CjzXyEsHKa2UM=
986-
github.com/hyperledger/fabric-x-sdk v0.0.0-20260521090010-c60d735b6023/go.mod h1:fOMpy5+efegUsET0d7RMH+CENyTpjZOD5D1bpZBWzos=
985+
github.com/hyperledger/fabric-x-sdk v0.0.0-20260529170509-baebbe88ff86 h1:dRYhIRAmiKWF7OsSi+SQGpha85K1/0yf8LzI1JqbNyo=
986+
github.com/hyperledger/fabric-x-sdk v0.0.0-20260529170509-baebbe88ff86/go.mod h1:fOMpy5+efegUsET0d7RMH+CENyTpjZOD5D1bpZBWzos=
987987
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
988988
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
989989
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=

0 commit comments

Comments
 (0)