Skip to content
This repository was archived by the owner on Oct 25, 2024. It is now read-only.

Commit 9102321

Browse files
Ruteriavalonche
authored andcommitted
Add remote relay connection for getting validator data (#11)
* Add remote relay connection for getting validator data * Add block submission to remote relay * Adjust readme
1 parent b1d792a commit 9102321

File tree

17 files changed

+903
-300
lines changed

17 files changed

+903
-300
lines changed

README.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,17 @@ Ropsten test network is based on the Ethash proof-of-work consensus algorithm. A
133133
it has certain extra overhead and is more susceptible to reorganization attacks due to the
134134
network's low difficulty/security.
135135

136-
Builder API has two hooks into geth:
136+
* Builder polls relay for the proposer registrations for the next epoch
137+
138+
Builder has two hooks into geth:
137139
* On forkchoice update, changing the payload attributes feeRecipient to the one registered for next slot's validator
138-
* On new sealed block, consuming the block as the next slot's proposed payload
140+
* On new sealed block, consuming the block as the next slot's proposed payload and submits it to the relay
141+
142+
Local relay is enabled by default and overwrites remote relay data. This is only meant for the testnets!
139143

140144
## Limitations
141145

142146
* Blocks are only built on forkchoice update call from beacon node
143-
* Only works post-Bellatrix, fork version is static
144147
* Does not accept external blocks
145148
* Does not have payload cache, only the latest block is available
146149

@@ -153,13 +156,14 @@ Builder API options:
153156
```
154157
$ geth --help
155158
BUILDER API OPTIONS:
156-
--builder.validator_checks Enable the validator checks
157-
--builder.secret_key value Builder API key used for signing headers (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11") [$BUILDER_SECRET_KEY]
159+
--builder.secret_key value Builder key used for signing blocks (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11") [$BUILDER_SECRET_KEY]
160+
--builder.relay_secret_key value Builder local relay API key used for signing headers (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11") [$BUILDER_RELAY_SECRET_KEY]
158161
--builder.listen_addr value Listening address for builder endpoint (default: ":28545") [$BUILDER_LISTEN_ADDR]
159-
--builder.genesis_fork_version value Gensis fork version (default: "0x02000000") [$BUILDER_GENESIS_FORK_VERSION]
160-
--builder.bellatrix_fork_version value Bellatrix fork version (default: "0x02000000") [$BUILDER_BELLATRIX_FORK_VERSION]
161-
--builder.genesis_validators_root value Genesis validators root of the network (static). For kiln use 0x99b09fcd43e5905236c370f184056bec6e6638cfc31a323b304fc4aa789cb4ad (default: "0x0000000000000000000000000000000000000000000000000000000000000000") [$BUILDER_GENESIS_VALIDATORS_ROOT]
162+
--builder.genesis_fork_version value Gensis fork version. For kiln use 0x70000069 (default: "0x00000000") [$BUILDER_GENESIS_FORK_VERSION]
163+
--builder.bellatrix_fork_version value Bellatrix fork version. For kiln use 0x70000071 (default: "0x02000000") [$BUILDER_BELLATRIX_FORK_VERSION]
164+
--builder.genesis_validators_root value Genesis validators root of the network. For kiln use 0x99b09fcd43e5905236c370f184056bec6e6638cfc31a323b304fc4aa789cb4ad (default: "0x0000000000000000000000000000000000000000000000000000000000000000") [$BUILDER_GENESIS_VALIDATORS_ROOT]
162165
--builder.beacon_endpoint value Beacon endpoint to connect to for beacon chain data (default: "http://127.0.0.1:5052") [$BUILDER_BEACON_ENDPOINT]
166+
--builder.remote_relay_endpoint value Relay endpoint to connect to for validator registration data, if not provided will expose validator registration locally [$BUILDER_REMOTE_RELAY_ENDPOINT]
163167
```
164168

165169
This will start `geth` in snap-sync mode with a DB memory allowance of 1GB, as the

builder/beacon_client.go

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

1717
type testBeaconClient struct {
1818
validator *ValidatorPrivateData
19+
slot uint64
1920
}
2021

2122
func (b *testBeaconClient) isValidator(pubkey PubkeyHex) bool {
@@ -24,8 +25,8 @@ func (b *testBeaconClient) isValidator(pubkey PubkeyHex) bool {
2425
func (b *testBeaconClient) getProposerForNextSlot(requestedSlot uint64) (PubkeyHex, error) {
2526
return PubkeyHex(hexutil.Encode(b.validator.Pk)), nil
2627
}
27-
func (b *testBeaconClient) onForkchoiceUpdate() (PubkeyHex, error) {
28-
return PubkeyHex(hexutil.Encode(b.validator.Pk)), nil
28+
func (b *testBeaconClient) onForkchoiceUpdate() (uint64, error) {
29+
return b.slot, nil
2930
}
3031

3132
type BeaconClient struct {
@@ -62,13 +63,13 @@ func (b *BeaconClient) getProposerForNextSlot(requestedSlot uint64) (PubkeyHex,
6263

6364
/* Returns next slot's proposer pubkey */
6465
// TODO: what happens if no block for previous slot - should still get next slot
65-
func (b *BeaconClient) onForkchoiceUpdate() (PubkeyHex, error) {
66+
func (b *BeaconClient) onForkchoiceUpdate() (uint64, error) {
6667
b.mu.Lock()
6768
defer b.mu.Unlock()
6869

6970
currentSlot, err := fetchCurrentSlot(b.endpoint)
7071
if err != nil {
71-
return PubkeyHex(""), err
72+
return 0, err
7273
}
7374

7475
nextSlot := currentSlot + 1
@@ -80,7 +81,7 @@ func (b *BeaconClient) onForkchoiceUpdate() (PubkeyHex, error) {
8081
// TODO: this should be prepared in advance, possibly just fetch for next epoch in advance
8182
slotProposerMap, err := fetchEpochProposersMap(b.endpoint, nextSlotEpoch)
8283
if err != nil {
83-
return PubkeyHex(""), err
84+
return 0, err
8485
}
8586

8687
b.currentEpoch = nextSlotEpoch
@@ -90,10 +91,10 @@ func (b *BeaconClient) onForkchoiceUpdate() (PubkeyHex, error) {
9091
nextSlotProposer, found := b.slotProposerMap[nextSlot]
9192
if !found {
9293
log.Error("inconsistent proposer mapping", "currentSlot", currentSlot, "slotProposerMap", b.slotProposerMap)
93-
return PubkeyHex(""), errors.New("inconsistent proposer mapping")
94+
return 0, errors.New("inconsistent proposer mapping")
9495
}
9596
b.nextSlotProposer = nextSlotProposer
96-
return nextSlotProposer, nil
97+
return nextSlot, nil
9798
}
9899

99100
func fetchCurrentSlot(endpoint string) (uint64, error) {

builder/beacon_client_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,11 @@ func TestOnForkchoiceUpdate(t *testing.T) {
204204
}`)
205205

206206
bc := NewBeaconClient(mbn.srv.URL)
207-
pubkeyHex, err := bc.onForkchoiceUpdate()
207+
slot, err := bc.onForkchoiceUpdate()
208208
require.NoError(t, err)
209-
require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b"), pubkeyHex)
209+
require.Equal(t, slot, uint64(32))
210210

211-
pubkeyHex, err = bc.getProposerForNextSlot(32)
211+
pubkeyHex, err := bc.getProposerForNextSlot(32)
212212
require.NoError(t, err)
213213
require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b"), pubkeyHex)
214214

@@ -221,9 +221,9 @@ func TestOnForkchoiceUpdate(t *testing.T) {
221221
mbn.headersCode = 404
222222
mbn.headersResp = []byte(`{ "code": 404, "message": "State not found" }`)
223223

224-
pubkeyHex, err = NewBeaconClient(mbn.srv.URL).onForkchoiceUpdate()
224+
slot, err = NewBeaconClient(mbn.srv.URL).onForkchoiceUpdate()
225225
require.EqualError(t, err, "State not found")
226-
require.Equal(t, PubkeyHex(""), pubkeyHex)
226+
require.Equal(t, slot, uint64(0))
227227

228228
// Check that client does not fetch new proposers if epoch did not change
229229
mbn.headersCode = 200
@@ -238,9 +238,9 @@ func TestOnForkchoiceUpdate(t *testing.T) {
238238
]
239239
}`)
240240

241-
pubkeyHex, err = bc.onForkchoiceUpdate()
241+
slot, err = bc.onForkchoiceUpdate()
242242
require.NoError(t, err, "")
243-
require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b"), pubkeyHex)
243+
require.Equal(t, slot, uint64(32))
244244

245245
mbn.headersResp = []byte(`{ "data": [ { "header": { "message": { "slot": "63", "proposer_index": "1" } } } ] }`)
246246
mbn.proposerDuties[2] = []byte(`{
@@ -253,16 +253,16 @@ func TestOnForkchoiceUpdate(t *testing.T) {
253253
]
254254
}`)
255255

256-
pubkeyHex, err = bc.onForkchoiceUpdate()
256+
slot, err = bc.onForkchoiceUpdate()
257257
require.NoError(t, err, "")
258-
require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74d"), pubkeyHex)
258+
require.Equal(t, slot, uint64(64))
259259

260260
pubkeyHex, err = bc.getProposerForNextSlot(64)
261261
require.NoError(t, err)
262262
require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74d"), pubkeyHex)
263263

264264
// Check proposers map error is routed out
265265
mbn.headersResp = []byte(`{ "data": [ { "header": { "message": { "slot": "65", "proposer_index": "1" } } } ] }`)
266-
pubkeyHex, err = bc.onForkchoiceUpdate()
266+
slot, err = bc.onForkchoiceUpdate()
267267
require.EqualError(t, err, "inconsistent proposer mapping")
268268
}

builder/builder.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package builder
2+
3+
import (
4+
"encoding/json"
5+
_ "os"
6+
7+
"github.com/ethereum/go-ethereum/common/hexutil"
8+
"github.com/ethereum/go-ethereum/core/beacon"
9+
"github.com/ethereum/go-ethereum/core/types"
10+
"github.com/ethereum/go-ethereum/log"
11+
12+
"github.com/flashbots/go-boost-utils/bls"
13+
boostTypes "github.com/flashbots/go-boost-utils/types"
14+
)
15+
16+
type PubkeyHex string
17+
18+
type ValidatorData struct {
19+
Pubkey PubkeyHex
20+
FeeRecipient boostTypes.Address `json:"feeRecipient"`
21+
GasLimit uint64 `json:"gasLimit"`
22+
Timestamp uint64 `json:"timestamp"`
23+
}
24+
25+
type IBeaconClient interface {
26+
isValidator(pubkey PubkeyHex) bool
27+
getProposerForNextSlot(requestedSlot uint64) (PubkeyHex, error)
28+
onForkchoiceUpdate() (uint64, error)
29+
}
30+
31+
type IRelay interface {
32+
SubmitBlock(msg *boostTypes.BuilderSubmitBlockRequest) error
33+
GetValidatorForSlot(nextSlot uint64) (ValidatorData, error)
34+
}
35+
36+
type Builder struct {
37+
beaconClient IBeaconClient
38+
relay IRelay
39+
40+
builderSecretKey *bls.SecretKey
41+
builderPublicKey boostTypes.PublicKey
42+
builderSigningDomain boostTypes.Domain
43+
}
44+
45+
func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain) *Builder {
46+
pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
47+
pk := boostTypes.PublicKey{}
48+
pk.FromSlice(pkBytes)
49+
50+
_, err := bc.onForkchoiceUpdate()
51+
if err != nil {
52+
log.Error("could not initialize beacon client", "err", err)
53+
}
54+
55+
return &Builder{
56+
beaconClient: bc,
57+
relay: relay,
58+
builderSecretKey: sk,
59+
builderPublicKey: pk,
60+
61+
builderSigningDomain: builderSigningDomain,
62+
}
63+
}
64+
65+
func (b *Builder) onForkchoice(payloadAttributes *beacon.PayloadAttributesV1) {
66+
dataJson, err := json.Marshal(payloadAttributes)
67+
if err == nil {
68+
log.Info("FCU", "data", string(dataJson))
69+
} else {
70+
log.Info("FCU", "data", payloadAttributes, "parsingError", err)
71+
72+
}
73+
74+
nextSlot, err := b.beaconClient.onForkchoiceUpdate()
75+
if err != nil {
76+
log.Error("FCU hook failed", "err", err)
77+
return
78+
}
79+
80+
if payloadAttributes != nil {
81+
payloadAttributes.Slot = nextSlot
82+
if vd, err := b.relay.GetValidatorForSlot(nextSlot); err == nil {
83+
payloadAttributes.SuggestedFeeRecipient = [20]byte(vd.FeeRecipient)
84+
payloadAttributes.GasLimit = vd.GasLimit
85+
}
86+
}
87+
}
88+
89+
func (b *Builder) newSealedBlock(data *beacon.ExecutableDataV1, block *types.Block, payloadAttributes *beacon.PayloadAttributesV1) {
90+
dataJson, err := json.Marshal(data)
91+
if err == nil {
92+
log.Info("newSealedBlock", "data", string(dataJson))
93+
} else {
94+
log.Info("newSealedBlock", "data", data, "parsingError", err)
95+
}
96+
payload, err := executableDataToExecutionPayload(data)
97+
if err != nil {
98+
log.Error("could not format execution payload", "err", err)
99+
return
100+
}
101+
102+
vd, err := b.relay.GetValidatorForSlot(payloadAttributes.Slot)
103+
if err != nil {
104+
log.Error("could not get validator while submitting block", "err", err, "slot", payloadAttributes.Slot)
105+
return
106+
}
107+
108+
pubkey, err := boostTypes.HexToPubkey(string(vd.Pubkey))
109+
if err != nil {
110+
log.Error("could not parse pubkey", "err", err, "pubkey", vd.Pubkey)
111+
return
112+
}
113+
114+
value := new(boostTypes.U256Str)
115+
err = value.FromBig(block.Profit)
116+
if err != nil {
117+
log.Error("could not set block value", "err", err)
118+
return
119+
}
120+
121+
blockBidMsg := boostTypes.BidTraceMessage{
122+
Slot: payloadAttributes.Slot,
123+
ParentHash: payload.ParentHash,
124+
BlockHash: payload.BlockHash,
125+
BuilderPubkey: b.builderPublicKey,
126+
ProposerPubkey: pubkey,
127+
ProposerFeeRecipient: boostTypes.Address(payloadAttributes.SuggestedFeeRecipient),
128+
Value: *value,
129+
}
130+
131+
signature, err := boostTypes.SignMessage(&blockBidMsg, b.builderSigningDomain, b.builderSecretKey)
132+
if err != nil {
133+
log.Error("could not sign builder bid", "err", err)
134+
return
135+
}
136+
137+
blockSubmitReq := boostTypes.BuilderSubmitBlockRequest{
138+
Signature: signature,
139+
Message: &blockBidMsg,
140+
ExecutionPayload: payload,
141+
}
142+
143+
err = b.relay.SubmitBlock(&blockSubmitReq)
144+
if err != nil {
145+
log.Error("could not submit block", "err", err)
146+
return
147+
}
148+
}
149+
150+
func executableDataToExecutionPayload(data *beacon.ExecutableDataV1) (*boostTypes.ExecutionPayload, error) {
151+
transactionData := make([]hexutil.Bytes, len(data.Transactions))
152+
for i, tx := range data.Transactions {
153+
transactionData[i] = hexutil.Bytes(tx)
154+
}
155+
156+
baseFeePerGas := new(boostTypes.U256Str)
157+
err := baseFeePerGas.FromBig(data.BaseFeePerGas)
158+
if err != nil {
159+
return nil, err
160+
}
161+
162+
return &boostTypes.ExecutionPayload{
163+
ParentHash: [32]byte(data.ParentHash),
164+
FeeRecipient: [20]byte(data.FeeRecipient),
165+
StateRoot: [32]byte(data.StateRoot),
166+
ReceiptsRoot: [32]byte(data.ReceiptsRoot),
167+
LogsBloom: boostTypes.Bloom(types.BytesToBloom(data.LogsBloom)),
168+
Random: [32]byte(data.Random),
169+
BlockNumber: data.Number,
170+
GasLimit: data.GasLimit,
171+
GasUsed: data.GasUsed,
172+
Timestamp: data.Timestamp,
173+
ExtraData: data.ExtraData,
174+
BaseFeePerGas: *baseFeePerGas,
175+
BlockHash: [32]byte(data.BlockHash),
176+
Transactions: transactionData,
177+
}, nil
178+
}

0 commit comments

Comments
 (0)