Skip to content

Commit 90d1926

Browse files
authored
improve usage of hayabusa network (#59)
* improve usage of hayabusa network * lint * Update client/client_hayabusa_test.go
1 parent 06ba35f commit 90d1926

File tree

5 files changed

+308
-1
lines changed

5 files changed

+308
-1
lines changed

client/client_hayabusa_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package client
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/require"
9+
"github.com/vechain/networkhub/hayabusa"
10+
"github.com/vechain/networkhub/preset"
11+
"github.com/vechain/networkhub/thorbuilder"
12+
"github.com/vechain/thor/v2/thor"
13+
"github.com/vechain/thor/v2/thorclient"
14+
)
15+
16+
// TestClientFourNodesHayabusa tests the client with a 4-node Hayabusa network.
17+
// This test verifies that the client can:
18+
// 1. Set up and start a 4-node Hayabusa network with immediate transition
19+
// 2. Wait for all nodes to connect and sync
20+
// 3. Deploy and execute smart contracts in post-hayabusa state
21+
// 4. Verify validator consensus and network health
22+
func TestClientFourNodesHayabusa(t *testing.T) {
23+
// Create the four nodes Hayabusa network with immediate transition
24+
fourNodesHayabusaNetwork := preset.LocalFourNodesHayabusa()
25+
fourNodesHayabusaNetwork.ThorBuilder.DownloadConfig = &thorbuilder.DownloadConfig{
26+
RepoUrl: "https://github.com/vechain/thor",
27+
Branch: "release/hayabusa",
28+
IsReusable: false,
29+
}
30+
31+
// Update ports to avoid collision with other tests
32+
basePort := 8700
33+
for _, node := range fourNodesHayabusaNetwork.Nodes {
34+
basePort++
35+
node.SetAPIAddr(fmt.Sprintf("127.0.0.1:%d", basePort))
36+
basePort++
37+
node.SetP2PListenPort(basePort)
38+
}
39+
40+
// Create client with the network
41+
c, err := New(fourNodesHayabusaNetwork)
42+
require.NoError(t, err)
43+
44+
require.NoError(t, c.Start())
45+
// Cleanup
46+
defer func() {
47+
if err := c.Stop(); err != nil {
48+
t.Logf("Warning: failed to stop client: %v", err)
49+
}
50+
}()
51+
52+
// Wait for all nodes to connect and sync
53+
t.Log("Waiting for Hayabusa nodes to connect and sync...")
54+
require.NoError(t, c.network.HealthCheck(4, 2*time.Minute))
55+
56+
// Test staker contract functionality to verify validators are active
57+
client := thorclient.New(c.network.Nodes[0].GetHTTPAddr())
58+
staker := hayabusa.NewStaker(client)
59+
60+
// Check firstActive to see if validators are now active
61+
validatorAddr, err := staker.FirstActive()
62+
require.NoError(t, err)
63+
t.Logf("FirstActive successful - Validator: %s", validatorAddr)
64+
65+
// Verify that one of our validators is now active
66+
expectedValidators := []thor.Address{
67+
*preset.SixNNAccount1.Address,
68+
*preset.SixNNAccount1.Address,
69+
*preset.SixNNAccount1.Address,
70+
*preset.SixNNAccount1.Address,
71+
}
72+
73+
validatorFound := false
74+
for _, expected := range expectedValidators {
75+
if validatorAddr == expected {
76+
validatorFound = true
77+
break
78+
}
79+
}
80+
require.True(t, validatorFound, "Active validator should be one of our registered validators, got %s", validatorAddr)
81+
82+
// Verify all nodes are producing blocks
83+
t.Log("Verifying all validator nodes are participating in consensus...")
84+
for i, node := range c.network.Nodes {
85+
nodeClient := thorclient.New(node.GetHTTPAddr())
86+
87+
// Check that each node can respond to queries
88+
block, err := nodeClient.Block("best")
89+
require.NoError(t, err, "Node %d should respond to block queries", i+1)
90+
require.Greater(t, block.Number, uint32(0), "Node %d should have produced blocks", i+1)
91+
92+
t.Logf("Node %d: Block %d, Validator nodes operational", i+1, block.Number)
93+
}
94+
95+
t.Log("Successfully tested Hayabusa network with 4 validator nodes!")
96+
}

hayabusa/hayabusa_compat.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package hayabusa
2+
3+
import (
4+
"encoding/hex"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/vechain/thor/v2/thor"
9+
"github.com/vechain/thor/v2/thorclient"
10+
)
11+
12+
// Method selectors for staker contract methods
13+
const (
14+
FirstActiveSelector = "0xd719835c" // firstActive()
15+
)
16+
17+
// Staker provides compatibility interface for staker contract interactions
18+
type Staker struct {
19+
client *thorclient.Client
20+
stakerAddr thor.Address
21+
}
22+
23+
// NewStaker creates a new staker instance
24+
func NewStaker(client *thorclient.Client) *Staker {
25+
return &Staker{
26+
client: client,
27+
stakerAddr: thor.BytesToAddress([]byte("Staker")),
28+
}
29+
}
30+
31+
// FirstActive calls the firstActive method on the staker contract
32+
func (s *Staker) FirstActive() (thor.Address, error) {
33+
payload := fmt.Sprintf(`{"clauses":[{"to":"%s","value":"0x0","data":"%s"}]}`,
34+
s.stakerAddr.String(), FirstActiveSelector)
35+
36+
result, statusCode, err := s.client.RawHTTPClient().RawHTTPPost("/accounts/*", []byte(payload))
37+
if err != nil {
38+
return thor.Address{}, fmt.Errorf("failed to call firstActive: %w", err)
39+
}
40+
41+
if statusCode != 200 {
42+
return thor.Address{}, fmt.Errorf("firstActive call failed with status %d", statusCode)
43+
}
44+
45+
// Parse the response
46+
var response []struct {
47+
Data string `json:"data"`
48+
Reverted bool `json:"reverted"`
49+
}
50+
51+
if err := json.Unmarshal(result, &response); err != nil {
52+
return thor.Address{}, fmt.Errorf("failed to parse firstActive response: %w", err)
53+
}
54+
55+
if len(response) == 0 || response[0].Reverted {
56+
return thor.Address{}, fmt.Errorf("firstActive call reverted")
57+
}
58+
59+
// Decode ABI-encoded address
60+
data := response[0].Data
61+
if len(data) < 2 || data[:2] != "0x" {
62+
return thor.Address{}, fmt.Errorf("invalid response data format: %s", data)
63+
}
64+
65+
dataBytes, err := hex.DecodeString(data[2:])
66+
if err != nil {
67+
return thor.Address{}, fmt.Errorf("failed to decode response data: %w", err)
68+
}
69+
70+
if len(dataBytes) < 32 {
71+
return thor.Address{}, fmt.Errorf("unexpected return length: %d", len(dataBytes))
72+
}
73+
74+
// Last 20 bytes of the 32-byte word are the address
75+
var validatorAddr thor.Address
76+
copy(validatorAddr[:], dataBytes[12:32])
77+
78+
return validatorAddr, nil
79+
}

internal/environments/docker/docker_node.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func (n *Node) Start() error {
4646
}
4747

4848
// Check if the Docker image is available locally
49-
_, _, err = cli.ImageInspectWithRaw(ctx, n.cfg.GetExecArtifact())
49+
_, err = cli.ImageInspect(ctx, n.cfg.GetExecArtifact())
5050
if err != nil {
5151
if client.IsErrNotFound(err) {
5252
// Pull the Docker image
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package preset
2+
3+
import (
4+
"github.com/vechain/networkhub/internal/environments"
5+
"github.com/vechain/networkhub/network"
6+
"github.com/vechain/networkhub/network/node"
7+
"github.com/vechain/networkhub/network/node/genesis"
8+
"github.com/vechain/networkhub/thorbuilder"
9+
thorgenesis "github.com/vechain/thor/v2/genesis"
10+
"github.com/vechain/thor/v2/thor"
11+
)
12+
13+
func LocalFourNodesHayabusaGenesis() *genesis.CustomGenesis {
14+
zero := uint32(0)
15+
return &genesis.CustomGenesis{
16+
LaunchTime: 1703180212,
17+
GasLimit: 10_000_000,
18+
ExtraData: "Local Four Nodes Network",
19+
Accounts: []thorgenesis.Account{
20+
{
21+
Address: *SixNNAccount1.Address,
22+
Balance: convToHexOrDecimal256(LargeBigValue),
23+
Energy: convToHexOrDecimal256(LargeBigValue),
24+
},
25+
{
26+
Address: *SixNNAccount2.Address,
27+
Balance: convToHexOrDecimal256(LargeBigValue),
28+
Energy: convToHexOrDecimal256(LargeBigValue),
29+
},
30+
{
31+
Address: *SixNNAccount3.Address,
32+
Balance: convToHexOrDecimal256(LargeBigValue),
33+
Energy: convToHexOrDecimal256(LargeBigValue),
34+
},
35+
{
36+
Address: *SixNNAccount4.Address,
37+
Balance: convToHexOrDecimal256(LargeBigValue),
38+
Energy: convToHexOrDecimal256(LargeBigValue),
39+
},
40+
},
41+
Authority: []thorgenesis.Authority{
42+
{
43+
MasterAddress: *SixNNAccount1.Address,
44+
EndorsorAddress: *SixNNAccount1.Address,
45+
Identity: thor.MustParseBytes32("0x0000000000000068747470733a2f2f617070732e7665636861696e2e6f72672f"),
46+
},
47+
{
48+
MasterAddress: *SixNNAccount2.Address,
49+
EndorsorAddress: *SixNNAccount2.Address,
50+
Identity: thor.MustParseBytes32("0x0000000000000068747470733a2f2f617070732e7665636861696e2e6f72672f"),
51+
},
52+
{
53+
MasterAddress: *SixNNAccount3.Address,
54+
EndorsorAddress: *SixNNAccount3.Address,
55+
Identity: thor.MustParseBytes32("0x0000000000000068747470733a2f2f617070732e7665636861696e2e6f72672f"),
56+
},
57+
{
58+
MasterAddress: *SixNNAccount4.Address,
59+
EndorsorAddress: *SixNNAccount4.Address,
60+
Identity: thor.MustParseBytes32("0x0000000000000068747470733a2f2f617070732e7665636861696e2e6f72672f"),
61+
},
62+
},
63+
Params: thorgenesis.Params{},
64+
ForkConfig: &genesis.CustomGenesisForkConfig{
65+
ForkConfig: thor.ForkConfig{
66+
VIP191: 0,
67+
ETH_CONST: 0,
68+
BLOCKLIST: 0,
69+
ETH_IST: 0,
70+
VIP214: 0,
71+
},
72+
AdditionalFields: map[string]uint32{
73+
"HAYABUSA": 0,
74+
},
75+
},
76+
Config: &genesis.Config{
77+
BlockInterval: 10,
78+
EpochLength: 10,
79+
SeederInterval: 10,
80+
ValidatorEvictionThreshold: 40,
81+
EvictionCheckInterval: 10,
82+
LowStakingPeriod: 10,
83+
MediumStakingPeriod: 20,
84+
HighStakingPeriod: 40,
85+
CooldownPeriod: 10,
86+
HayabusaTP: &zero,
87+
},
88+
}
89+
}
90+
91+
func LocalFourNodesHayabusa() *network.Network {
92+
thorBuilderCfg := thorbuilder.DefaultConfig()
93+
thorBuilderCfg.DownloadConfig.Branch = "release/hayabusa"
94+
thorBuilderCfg.BuildConfig.ReuseBinary = false
95+
96+
gen := LocalFourNodesHayabusaGenesis()
97+
98+
netwk := &network.Network{
99+
Environment: environments.Local,
100+
BaseID: "hayabusa-four-nodes",
101+
ThorBuilder: thorBuilderCfg,
102+
Nodes: []node.Config{
103+
&node.BaseNode{
104+
ID: "hayabusa-node-1",
105+
Key: SixNNAccount1.PrivateKeyString(),
106+
Genesis: gen,
107+
FakeExecution: false,
108+
},
109+
&node.BaseNode{
110+
ID: "hayabusa-node-2",
111+
Key: SixNNAccount2.PrivateKeyString(),
112+
Genesis: gen,
113+
},
114+
&node.BaseNode{
115+
ID: "hayabusa-node-3",
116+
Key: SixNNAccount3.PrivateKeyString(),
117+
Genesis: gen,
118+
},
119+
&node.BaseNode{
120+
ID: "hayabusa-node-4",
121+
Key: SixNNAccount4.PrivateKeyString(),
122+
Genesis: gen,
123+
},
124+
},
125+
}
126+
return netwk
127+
}

utils/common/common.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package common
22

33
import (
44
"crypto/ecdsa"
5+
"fmt"
56
"time"
67

78
"github.com/ethereum/go-ethereum/crypto"
@@ -39,6 +40,10 @@ type Account struct {
3940
PrivateKey *ecdsa.PrivateKey
4041
}
4142

43+
func (a Account) PrivateKeyString() string {
44+
return fmt.Sprintf("%x", a.PrivateKey.D.Bytes())
45+
}
46+
4247
type TxSendResult struct {
4348
ID *thor.Bytes32
4449
}

0 commit comments

Comments
 (0)