Skip to content

Commit 022bfbc

Browse files
committed
Merge branch 'andromeda-to-barnard' into andromeda-to-governance
# Conflicts: # common/constants.go
2 parents 8a584a8 + 50585fb commit 022bfbc

735 files changed

Lines changed: 45778 additions & 9576 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build_and_run_chain_simulator_and_execute_system_test.yml

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,27 @@ jobs:
6262
with:
6363
github-token: ${{ secrets.GITHUB_TOKEN }}
6464
script: |
65-
// Get the latest comment
65+
// Get all comments
6666
const comments = await github.rest.issues.listComments({
6767
owner: context.repo.owner,
6868
repo: context.repo.repo,
6969
issue_number: context.issue.number,
7070
});
7171
72-
const lastComment = comments.data.pop(); // Get the last comment
72+
// Find the last comment that contains 'Run Tests:'
73+
let lastTestComment = null;
74+
for (let i = comments.data.length - 1; i >= 0; i--) {
75+
if (comments.data[i].body.includes('Run Tests:')) {
76+
lastTestComment = comments.data[i];
77+
break;
78+
}
79+
}
7380
74-
if (lastComment && lastComment.body.includes('Run Tests:')) {
75-
const body = lastComment.body.trim();
81+
if (lastTestComment) {
82+
const body = lastTestComment.body.trim();
7683
core.setOutput('latest_comment', body);
7784
78-
// Parse the branches from the last comment
85+
// Parse the branches from the last test comment
7986
const simulatorBranchMatch = body.match(/mx-chain-simulator-go:\s*(\S+)/);
8087
const testingSuiteBranchMatch = body.match(/mx-chain-testing-suite:\s*(\S+)/);
8188
@@ -86,8 +93,11 @@ jobs:
8693
if (testingSuiteBranchMatch) {
8794
core.exportVariable('MX_CHAIN_TESTING_SUITE_TARGET_BRANCH', testingSuiteBranchMatch[1]);
8895
}
96+
97+
// Log which comment was used for configuration
98+
core.info(`Found 'Run Tests:' comment from ${lastTestComment.user.login} at ${lastTestComment.created_at}`);
8999
} else {
90-
core.info('The last comment does not contain "Run Tests:". Skipping branch override.');
100+
core.info('No comment containing "Run Tests:" was found. Using default branch settings.');
91101
}
92102
93103

@@ -146,6 +156,82 @@ jobs:
146156
go build
147157
echo "CHAIN_SIMULATOR_BUILD_PATH=$(pwd)" >> $GITHUB_ENV
148158
159+
- name: Initialize Chain Simulator
160+
run: |
161+
cd mx-chain-simulator-go/cmd/chainsimulator
162+
163+
# Start ChainSimulator with minimal args to initialize configs
164+
INIT_LOG_FILE="/tmp/chainsim_init.log"
165+
echo "Starting ChainSimulator initialization process..."
166+
./chainsimulator > $INIT_LOG_FILE 2>&1 &
167+
INIT_PROCESS_PID=$!
168+
169+
# Verify the process is running
170+
if ! ps -p $INIT_PROCESS_PID > /dev/null; then
171+
echo "Failed to start ChainSimulator process"
172+
cat $INIT_LOG_FILE
173+
exit 1
174+
fi
175+
176+
# Wait for the initialization to complete - look for multiple possible success patterns
177+
INIT_COMPLETED=false
178+
RETRY_COUNT=0
179+
MAX_RETRIES=60 # Increase timeout to 60 seconds
180+
181+
echo "Waiting for ChainSimulator initialization..."
182+
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
183+
# Check for any of these success patterns
184+
if grep -q "starting as observer node" $INIT_LOG_FILE || \
185+
grep -q "ChainSimulator started successfully" $INIT_LOG_FILE || \
186+
grep -q "initialized the node" $INIT_LOG_FILE || \
187+
grep -q "Node is running" $INIT_LOG_FILE; then
188+
INIT_COMPLETED=true
189+
echo "ChainSimulator initialization completed successfully"
190+
break
191+
fi
192+
193+
# If there's a known fatal error, exit early
194+
if grep -q "fatal error" $INIT_LOG_FILE || grep -q "panic:" $INIT_LOG_FILE; then
195+
echo "Fatal error detected during initialization:"
196+
grep -A 10 -E "fatal error|panic:" $INIT_LOG_FILE
197+
break
198+
fi
199+
200+
# Print progress every 10 seconds
201+
if [ $((RETRY_COUNT % 10)) -eq 0 ]; then
202+
echo "Still waiting for initialization... ($RETRY_COUNT seconds elapsed)"
203+
tail -5 $INIT_LOG_FILE
204+
fi
205+
206+
RETRY_COUNT=$((RETRY_COUNT+1))
207+
sleep 1
208+
done
209+
210+
# Kill the initialization process - try graceful shutdown first
211+
echo "Stopping initialization process (PID: $INIT_PROCESS_PID)..."
212+
kill -TERM $INIT_PROCESS_PID 2>/dev/null || true
213+
sleep 3
214+
215+
# Check if process still exists and force kill if needed
216+
if ps -p $INIT_PROCESS_PID > /dev/null 2>&1; then
217+
echo "Process still running, forcing kill..."
218+
kill -9 $INIT_PROCESS_PID 2>/dev/null || true
219+
sleep 1
220+
fi
221+
222+
if [ "$INIT_COMPLETED" != "true" ]; then
223+
echo "ChainSimulator initialization failed after $MAX_RETRIES seconds"
224+
echo "Last 20 lines of log:"
225+
tail -20 $INIT_LOG_FILE
226+
exit 1
227+
fi
228+
229+
# Create a marker file to indicate successful initialization
230+
touch /tmp/chain_simulator_initialized.lock
231+
echo "Chain Simulator successfully initialized"
232+
233+
echo "Initialization log stored at: $INIT_LOG_FILE"
234+
149235
- name: Checkout mx-chain-testing-suite
150236
uses: actions/checkout@v4
151237
with:

api/errors/errors.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ var ErrGetValueForKey = errors.New("get value for key error")
2828
// ErrGetKeyValuePairs signals an error in getting the key-value pairs of a key for an account
2929
var ErrGetKeyValuePairs = errors.New("get key-value pairs error")
3030

31+
// ErrIterateKeys signals an error in iterating over the keys of an account
32+
var ErrIterateKeys = errors.New("iterate keys error")
33+
3134
// ErrGetESDTBalance signals an error in getting esdt balance for given address
3235
var ErrGetESDTBalance = errors.New("get esdt balance for account error")
3336

@@ -43,6 +46,12 @@ var ErrGetESDTNFTData = errors.New("get esdt nft data for account error")
4346
// ErrEmptyAddress signals that an empty address was provided
4447
var ErrEmptyAddress = errors.New("address is empty")
4548

49+
// ErrEmptyNumKeys signals that an empty numKeys was provided
50+
var ErrEmptyNumKeys = errors.New("numKeys is empty")
51+
52+
// ErrEmptyCheckpointId signals that an empty checkpointId was provided
53+
var ErrEmptyCheckpointId = errors.New("checkpointId is empty")
54+
4655
// ErrEmptyKey signals that an empty key was provided
4756
var ErrEmptyKey = errors.New("key is empty")
4857

api/groups/addressGroup.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const (
3232
getRegisteredNFTsPath = "/:address/registered-nfts"
3333
getESDTNFTDataPath = "/:address/nft/:tokenIdentifier/nonce/:nonce"
3434
getGuardianData = "/:address/guardian-data"
35+
iterateKeysPath = "/iterate-keys"
3536
urlParamOnFinalBlock = "onFinalBlock"
3637
urlParamOnStartOfEpoch = "onStartOfEpoch"
3738
urlParamBlockNonce = "blockNonce"
@@ -55,6 +56,7 @@ type addressFacadeHandler interface {
5556
GetESDTsWithRole(address string, role string, options api.AccountQueryOptions) ([]string, api.BlockInfo, error)
5657
GetAllESDTTokens(address string, options api.AccountQueryOptions) (map[string]*esdt.ESDigitalToken, api.BlockInfo, error)
5758
GetKeyValuePairs(address string, options api.AccountQueryOptions) (map[string]string, api.BlockInfo, error)
59+
IterateKeys(address string, numKeys uint, iteratorState [][]byte, options api.AccountQueryOptions) (map[string]string, [][]byte, api.BlockInfo, error)
5860
GetGuardianData(address string, options api.AccountQueryOptions) (api.GuardianData, api.BlockInfo, error)
5961
IsDataTrieMigrated(address string, options api.AccountQueryOptions) (bool, error)
6062
IsInterfaceNil() bool
@@ -134,6 +136,11 @@ func NewAddressGroup(facade addressFacadeHandler) (*addressGroup, error) {
134136
Method: http.MethodGet,
135137
Handler: ag.getKeyValuePairs,
136138
},
139+
{
140+
Path: iterateKeysPath,
141+
Method: http.MethodPost,
142+
Handler: ag.iterateKeys,
143+
},
137144
{
138145
Path: getESDTBalancePath,
139146
Method: http.MethodGet,
@@ -327,7 +334,7 @@ func (ag *addressGroup) getGuardianData(c *gin.Context) {
327334
shared.RespondWithSuccess(c, gin.H{"guardianData": guardianData, "blockInfo": blockInfo})
328335
}
329336

330-
// addressGroup returns all the key-value pairs for the given address
337+
// getKeyValuePairs returns all the key-value pairs for the given address
331338
func (ag *addressGroup) getKeyValuePairs(c *gin.Context) {
332339
addr, options, err := extractBaseParams(c)
333340
if err != nil {
@@ -344,6 +351,47 @@ func (ag *addressGroup) getKeyValuePairs(c *gin.Context) {
344351
shared.RespondWithSuccess(c, gin.H{"pairs": value, "blockInfo": blockInfo})
345352
}
346353

354+
// IterateKeysRequest defines the request structure for iterating keys
355+
type IterateKeysRequest struct {
356+
Address string `json:"address"`
357+
NumKeys uint `json:"numKeys"`
358+
IteratorState [][]byte `json:"iteratorState"`
359+
}
360+
361+
// iterateKeys iterates keys for the given address
362+
func (ag *addressGroup) iterateKeys(c *gin.Context) {
363+
var iterateKeysRequest = &IterateKeysRequest{}
364+
err := c.ShouldBindJSON(&iterateKeysRequest)
365+
if err != nil {
366+
shared.RespondWithValidationError(c, errors.ErrValidation, err)
367+
return
368+
}
369+
370+
if len(iterateKeysRequest.Address) == 0 {
371+
shared.RespondWithValidationError(c, errors.ErrValidation, errors.ErrEmptyAddress)
372+
return
373+
}
374+
375+
options, err := extractAccountQueryOptions(c)
376+
if err != nil {
377+
shared.RespondWithValidationError(c, errors.ErrIterateKeys, err)
378+
return
379+
}
380+
381+
value, newIteratorState, blockInfo, err := ag.getFacade().IterateKeys(
382+
iterateKeysRequest.Address,
383+
iterateKeysRequest.NumKeys,
384+
iterateKeysRequest.IteratorState,
385+
options,
386+
)
387+
if err != nil {
388+
shared.RespondWithInternalError(c, errors.ErrIterateKeys, err)
389+
return
390+
}
391+
392+
shared.RespondWithSuccess(c, gin.H{"pairs": value, "newIteratorState": newIteratorState, "blockInfo": blockInfo})
393+
}
394+
347395
// getESDTBalance returns the balance for the given address and esdt token
348396
func (ag *addressGroup) getESDTBalance(c *gin.Context) {
349397
addr, tokenIdentifier, options, err := extractGetESDTBalanceParams(c)

api/groups/addressGroup_test.go

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ type esdtTokensCompleteResponseData struct {
112112
type esdtTokensCompleteResponse struct {
113113
Data esdtTokensCompleteResponseData `json:"data"`
114114
Error string `json:"error"`
115-
Code string
115+
Code string `json:"code"`
116116
}
117117

118118
type keyValuePairsResponseData struct {
@@ -122,7 +122,17 @@ type keyValuePairsResponseData struct {
122122
type keyValuePairsResponse struct {
123123
Data keyValuePairsResponseData `json:"data"`
124124
Error string `json:"error"`
125-
Code string
125+
Code string `json:"code"`
126+
}
127+
128+
type iterateKeysResponseData struct {
129+
Pairs map[string]string `json:"pairs"`
130+
NewIteratorState [][]byte `json:"newIteratorState"`
131+
}
132+
type iterateKeysResponse struct {
133+
Data iterateKeysResponseData `json:"data"`
134+
Error string `json:"error"`
135+
Code string `json:"code"`
126136
}
127137

128138
type esdtRolesResponseData struct {
@@ -132,7 +142,7 @@ type esdtRolesResponseData struct {
132142
type esdtRolesResponse struct {
133143
Data esdtRolesResponseData `json:"data"`
134144
Error string `json:"error"`
135-
Code string
145+
Code string `json:"code"`
136146
}
137147

138148
type usernameResponseData struct {
@@ -662,6 +672,106 @@ func TestAddressGroup_getKeyValuePairs(t *testing.T) {
662672
})
663673
}
664674

675+
func TestAddressGroup_iterateKeys(t *testing.T) {
676+
t.Parallel()
677+
678+
t.Run("invalid body should error",
679+
testErrorScenario("/address/iterate-keys", "POST", bytes.NewBuffer([]byte("invalid body")),
680+
formatExpectedErr(apiErrors.ErrValidation, errors.New("invalid character 'i' looking for beginning of value"))))
681+
t.Run("empty address should error", func(t *testing.T) {
682+
t.Parallel()
683+
684+
body := &groups.IterateKeysRequest{
685+
Address: "",
686+
}
687+
bodyBytes, _ := json.Marshal(body)
688+
testAddressGroup(
689+
t,
690+
&mock.FacadeStub{},
691+
"/address/iterate-keys",
692+
"POST",
693+
bytes.NewBuffer(bodyBytes),
694+
http.StatusBadRequest,
695+
formatExpectedErr(apiErrors.ErrValidation, apiErrors.ErrEmptyAddress),
696+
)
697+
})
698+
t.Run("invalid query options should error", func(t *testing.T) {
699+
t.Parallel()
700+
701+
body := &groups.IterateKeysRequest{
702+
Address: "erd1",
703+
}
704+
bodyBytes, _ := json.Marshal(body)
705+
testAddressGroup(
706+
t,
707+
&mock.FacadeStub{},
708+
"/address/iterate-keys?blockNonce=not-uint64",
709+
"POST",
710+
bytes.NewBuffer(bodyBytes),
711+
http.StatusBadRequest,
712+
formatExpectedErr(apiErrors.ErrIterateKeys, apiErrors.ErrBadUrlParams),
713+
)
714+
})
715+
t.Run("with node fail should err", func(t *testing.T) {
716+
t.Parallel()
717+
718+
body := &groups.IterateKeysRequest{
719+
Address: "erd1",
720+
}
721+
bodyBytes, _ := json.Marshal(body)
722+
facade := &mock.FacadeStub{
723+
IterateKeysCalled: func(address string, numKeys uint, iteratorState [][]byte, options api.AccountQueryOptions) (map[string]string, [][]byte, api.BlockInfo, error) {
724+
return nil, nil, api.BlockInfo{}, expectedErr
725+
},
726+
}
727+
testAddressGroup(
728+
t,
729+
facade,
730+
"/address/iterate-keys",
731+
"POST",
732+
bytes.NewBuffer(bodyBytes),
733+
http.StatusInternalServerError,
734+
formatExpectedErr(apiErrors.ErrIterateKeys, expectedErr),
735+
)
736+
})
737+
t.Run("should work", func(t *testing.T) {
738+
t.Parallel()
739+
740+
pairs := map[string]string{
741+
"k1": "v1",
742+
"k2": "v2",
743+
}
744+
745+
body := &groups.IterateKeysRequest{
746+
Address: "erd1",
747+
NumKeys: 10,
748+
IteratorState: [][]byte{[]byte("starting"), []byte("state")},
749+
}
750+
newIteratorState := [][]byte{[]byte("new"), []byte("state")}
751+
bodyBytes, _ := json.Marshal(body)
752+
facade := &mock.FacadeStub{
753+
IterateKeysCalled: func(address string, numKeys uint, iteratorState [][]byte, options api.AccountQueryOptions) (map[string]string, [][]byte, api.BlockInfo, error) {
754+
assert.Equal(t, body.Address, address)
755+
assert.Equal(t, body.NumKeys, numKeys)
756+
assert.Equal(t, body.IteratorState, iteratorState)
757+
return pairs, newIteratorState, api.BlockInfo{}, nil
758+
},
759+
}
760+
761+
response := &iterateKeysResponse{}
762+
loadAddressGroupResponse(
763+
t,
764+
facade,
765+
"/address/iterate-keys",
766+
"POST",
767+
bytes.NewBuffer(bodyBytes),
768+
response,
769+
)
770+
assert.Equal(t, pairs, response.Data.Pairs)
771+
assert.Equal(t, newIteratorState, response.Data.NewIteratorState)
772+
})
773+
}
774+
665775
func TestAddressGroup_getESDTBalance(t *testing.T) {
666776
t.Parallel()
667777

@@ -1143,6 +1253,7 @@ func getAddressRoutesConfig() config.ApiRoutesConfig {
11431253
{Name: "/:address/username", Open: true},
11441254
{Name: "/:address/code-hash", Open: true},
11451255
{Name: "/:address/keys", Open: true},
1256+
{Name: "/iterate-keys", Open: true},
11461257
{Name: "/:address/key/:key", Open: true},
11471258
{Name: "/:address/esdt", Open: true},
11481259
{Name: "/:address/esdts/roles", Open: true},

0 commit comments

Comments
 (0)