Skip to content

Commit

Permalink
feat(builder): SSZ Merkle Multiproofs
Browse files Browse the repository at this point in the history
  • Loading branch information
thedevbirb committed Jun 12, 2024
1 parent 3810a53 commit 8441feb
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 215 deletions.
83 changes: 10 additions & 73 deletions builder/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ import (
"errors"
"fmt"
"io"
"math"
"math/big"
"net/http"
_ "os"
"slices"
"strings"
"sync"
"time"
Expand All @@ -28,7 +26,6 @@ import (
"github.com/attestantio/go-eth2-client/spec/capella"
"github.com/attestantio/go-eth2-client/spec/deneb"
"github.com/attestantio/go-eth2-client/spec/phase0"
utilbellatrix "github.com/attestantio/go-eth2-client/util/bellatrix"
"github.com/chainbound/shardmap"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -460,85 +457,25 @@ func (b *Builder) onSealedBlock(opts SubmitBlockOpts, constraints types.HashToCo
var versionedBlockRequestWithPreconfsProofs *common.VersionedSubmitBlockRequestWithProofs

if len(constraints) > 0 {
EmitBoltDemoEvent(fmt.Sprintf("sealing block %d with %d constraints", opts.Block.Number(), len(constraints)))
log.Info(fmt.Sprintf("[BOLT]: Sealing block with %d preconfirmed transactions for block %d", len(constraints), opts.Block.Number()))
payloadTransactions := opts.Block.Transactions()

// BOLT: sanity check: verify that the block actually contains the preconfirmed transactions
for hash, constraint := range constraints {
if !slices.Contains(payloadTransactions, constraint.Tx) {
log.Error(fmt.Sprintf("[BOLT]: Preconfirmed transaction %s not found in block %s. Block can't be sent", hash, opts.Block.Hash()))
return nil
}
}

// BOLT: generate merkle tree from payload transactions (we need raw RLP bytes for this)
rawTxs := make([]bellatrix.Transaction, len(payloadTransactions))
for i, tx := range payloadTransactions {
raw, err := tx.MarshalBinary()
if err != nil {
log.Error("[BOLT]: could not marshal transaction", "txHash", tx.Hash(), "err", err)
continue
}
rawTxs[i] = bellatrix.Transaction(raw)
}

log.Info(fmt.Sprintf("[BOLT]: Generated %d raw transactions for merkle tree", len(rawTxs)))
bellatrixPayloadTxs := utilbellatrix.ExecutionPayloadTransactions{Transactions: rawTxs}

rootNode, err := bellatrixPayloadTxs.GetTree()
if err != nil {
log.Error("[BOLT]: could not get tree from transactions", "err", err)
}

// BOLT: Set the value of nodes. This is MANDATORY for the proof calculation
// to output the leaf correctly. This is also never documented in fastssz. -__-
rootNode.Hash()

// BOLT: calculate merkle proofs for preconfirmed transactions
preconfirmationsProofs := make([]*common.PreconfirmationWithProof, 0, len(constraints))
message := fmt.Sprintf("sealing block %d with %d constraints", opts.Block.Number(), len(constraints))
log.Info(message)
EmitBoltDemoEvent(message)

timeStart := time.Now()
for hash := range constraints {
// get the index of the preconfirmed transaction in the block
preconfIndex := slices.IndexFunc(payloadTransactions, func(tx *types.Transaction) bool { return tx.Hash() == hash })
if preconfIndex == -1 {
log.Error(fmt.Sprintf("Preconfirmed transaction %s not found in block %s", hash, opts.Block.Hash()))
log.Error(fmt.Sprintf("block has %v transactions", len(payloadTransactions)))
continue
}

// using our gen index formula: 2 * 2^20 + preconfIndex
generalizedIndex := int(math.Pow(float64(2), float64(21))) + preconfIndex

log.Info(fmt.Sprintf("[BOLT]: Calculating merkle proof for preconfirmed transaction %s with index %d. Preconf index: %d",
hash, generalizedIndex, preconfIndex))

timeStartInner := time.Now()
proof, err := rootNode.Prove(generalizedIndex)
if err != nil {
log.Error("[BOLT]: could not calculate merkle proof for preconfirmed transaction", "txHash", hash, "err", err)
continue
}
log.Info(fmt.Sprintf("[BOLT]: Calculated merkle proof for preconf %s in %s", hash, time.Since(timeStartInner)))
log.Info(fmt.Sprintf("[BOLT]: LEAF: %x, Is leaf nil? %v", proof.Leaf, proof.Leaf == nil))

merkleProof := new(common.SerializedMerkleProof)
merkleProof.FromFastSszProof(proof)
inclusionProof, _, err := CalculateMerkleMultiProofs(opts.Block.Transactions(), constraints)
timeForProofs := time.Since(timeStart)

preconfirmationsProofs = append(preconfirmationsProofs, &common.PreconfirmationWithProof{
TxHash: phase0.Hash32(hash),
MerkleProof: merkleProof,
})
if err != nil {
log.Error("could not calculate merkle multiproofs", "err", err)
return err
}
timeForProofs := time.Since(timeStart)

// BOLT: send event to web demo
EmitBoltDemoEvent(fmt.Sprintf("created %d merkle proofs for block %d in %v", len(constraints), opts.Block.Number(), timeForProofs))

versionedBlockRequestWithPreconfsProofs = &common.VersionedSubmitBlockRequestWithProofs{
Inner: versionedBlockRequest,
Proofs: preconfirmationsProofs,
Proofs: inclusionProof,
}
}

Expand All @@ -558,7 +495,7 @@ func (b *Builder) onSealedBlock(opts SubmitBlockOpts, constraints types.HashToCo
// NOTE: we can ignore preconfs for `processBuiltBlock`
go b.processBuiltBlock(opts.Block, opts.BlockValue, opts.OrdersClosedAt, opts.SealedAt, opts.CommitedBundles, opts.AllBundles, opts.UsedSbundles, &blockBidMsg)
if versionedBlockRequestWithPreconfsProofs != nil {
log.Info(fmt.Sprintf("[BOLT]: Sending block with %d proofs to relay %s", len(versionedBlockRequestWithPreconfsProofs.Proofs), versionedBlockRequestWithPreconfsProofs.String()))
log.Info(fmt.Sprintf("[BOLT]: Sending block with %d proofs to relay %s", len(versionedBlockRequestWithPreconfsProofs.Proofs.TransactionHashes), versionedBlockRequestWithPreconfsProofs.String()))
err = b.relay.SubmitBlockWithProofs(versionedBlockRequestWithPreconfsProofs, opts.ValidatorData)
} else if len(constraints) == 0 {
// If versionedBlockRequestWithPreconfsProofs is nil and no constraints, then we don't have proofs to send
Expand Down
185 changes: 92 additions & 93 deletions builder/builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"math"
"math/big"
"net/http"
"slices"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -437,98 +436,98 @@ func TestGenerateSSZProofs(t *testing.T) {
}
}

func TestGenerateSSZProofs2(t *testing.T) {
raw := `["0x02f872833018240385e8d4a5100085e8d4a5100082520894deaddeaddeaddeaddeaddeaddeaddeaddeaddead8306942080c001a042696cf1ef039cf23f51b8348c7fcda961727dd6350992b06c6139cf2b66ed18a012252715233c0cb9803bf827942f619b4a6857a9bfb214ef2feba983d1b5ed0e","0x02f90176833018242585012a05f2008512a05f2000830249f0946c6340ba1dc72c59197825cd94eccc1f9c67416e80b901040cc7326300000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000008ffb6787e8ad80000000000000000000000000000b77d61ea79c7ea8bfa03d3604ce5eabfb95c2ab20000000000000000000000002c57d1cfc6d5f8e4182a56b4cf75421472ebaea4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000001cd4af4a9bf33474c802d31790a195335f7a9ab8000000000000000000000000d676af79742bcaeb4a71cf62b85d5ba2d1deaf86c001a08d03bdca0c1647263ef73d916e949ccc53284c6fa208c3fa4f9ddfe67d9c45dfa055be5793b42f1818716276033eb36420fa4fb4e3efabd0bbb01c489f7d9cd43c","0x02f86c8330182404801b825208948943545177806ed17b9f23f0a21ee5948ecaa7768701e71eeda00c3080c001a05918a7b26059059e3fc130bfeb42707bcdab9efaabad518f02f062a5d79e0ae7a06c3f27be896c38ed49a6a943050680b8bb1544b77a4df75c62ef75b357b27c7b"]`
preconfRaw := "02f872833018240385e8d4a5100085e8d4a5100082520894deaddeaddeaddeaddeaddeaddeaddeaddeaddead8306942080c001a042696cf1ef039cf23f51b8348c7fcda961727dd6350992b06c6139cf2b66ed18a012252715233c0cb9803bf827942f619b4a6857a9bfb214ef2feba983d1b5ed0e"

// prepare the decoded preconf
preconf := new(types.Transaction)
bytes, err := hex.DecodeString(preconfRaw)
require.NoError(t, err)
err = preconf.UnmarshalBinary(bytes)
require.NoError(t, err)
require.NotNil(t, preconf)
preconfs := []*types.Transaction{preconf}

transactionsRaw := new([]string)
err = json.Unmarshal([]byte(raw), transactionsRaw)
require.NoError(t, err)

// prepare the entire payload
payloadTransactions := make([]*types.Transaction, 0, 10)
for _, tx := range *transactionsRaw {
rawBytes, _ := hex.DecodeString(tx[2:])
transaction := new(types.Transaction)
require.NoError(t, err)
err = transaction.UnmarshalBinary(rawBytes)
require.NoError(t, err)
require.NotNil(t, transaction)
payloadTransactions = append(payloadTransactions, transaction)
}

t.Logf("Transactions: len %d\n", len(payloadTransactions))

// BUILDER CODE starts here
if payloadTransactions[0].Hash() != preconf.Hash() {
t.Fatal("Preconf transaction doesn't match with the first transaction in the payload")
}

// BOLT: generate merkle tree from payload transactions (we need raw RLP bytes for this)
rawTxs := make([]bellatrix.Transaction, len(payloadTransactions))
for i, tx := range payloadTransactions {
raw, err := tx.MarshalBinary()
if err != nil {
log.Error("[BOLT]: could not marshal transaction", "txHash", tx.Hash(), "err", err)
continue
}
rawTxs[i] = bellatrix.Transaction(raw)
}

bellatrixPayloadTxs := utilbellatrix.ExecutionPayloadTransactions{Transactions: rawTxs}

rootNode, err := bellatrixPayloadTxs.GetTree()
require.NoError(t, err, "could not get raw txs tree")

t.Logf("rootNode: %x", rootNode.Hash()) // e557527d9e7d97eaf4592637901e02a31c09c27d6076c27970799f418e47deab

// BOLT: calculate merkle proofs for preconfirmed transactions
preconfirmationsProofs := make([]*common.PreconfirmationWithProof, 0, len(preconfs))

for i, preconf := range preconfs {
// get the index of the preconfirmed transaction in the block
preconfIndex := slices.IndexFunc(payloadTransactions, func(tx *types.Transaction) bool { return tx.Hash() == preconf.Hash() })
if preconfIndex == -1 {
log.Error(fmt.Sprintf("Preconfirmed transaction %s not found", preconf.Hash()))
log.Error(fmt.Sprintf("block has %v transactions", len(payloadTransactions)))
continue
}

// using our gen index formula: 2 * 2^20 + preconfIndex
generalizedIndex := int(math.Pow(float64(2), float64(21))) + preconfIndex

t.Logf("[BOLT]: Calculating merkle proof for preconfirmed transaction %s with index %d. Preconf index: %d",
preconf.Hash(), generalizedIndex, preconfIndex)

timeStart := time.Now()
proof, err := rootNode.Prove(generalizedIndex)
require.NoError(t, err, "could not generate proof for preconfirmed transaction")

t.Logf("[BOLT]: Calculated merkle proof for preconf %s in %s", preconf.Hash(), time.Since(timeStart))
t.Logf("[BOLT]: LEAF: %x, Is leaf nil? %v", proof.Leaf, proof.Leaf == nil)

merkleProof := new(common.SerializedMerkleProof)
merkleProof.FromFastSszProof(proof)

preconfirmationsProofs = append(preconfirmationsProofs, &common.PreconfirmationWithProof{
TxHash: phase0.Hash32(preconf.Hash()),
MerkleProof: merkleProof,
})

t.Logf("[BOLT]: Added merkle proof for preconfirmed transaction %s", preconfirmationsProofs[i])
}

t.Logf("[BOLT]: Generated %d merkle proofs for preconfirmed transactions", len(preconfirmationsProofs))
}
// func TestGenerateSSZProofs2(t *testing.T) {
// raw := `["0x02f872833018240385e8d4a5100085e8d4a5100082520894deaddeaddeaddeaddeaddeaddeaddeaddeaddead8306942080c001a042696cf1ef039cf23f51b8348c7fcda961727dd6350992b06c6139cf2b66ed18a012252715233c0cb9803bf827942f619b4a6857a9bfb214ef2feba983d1b5ed0e","0x02f90176833018242585012a05f2008512a05f2000830249f0946c6340ba1dc72c59197825cd94eccc1f9c67416e80b901040cc7326300000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000008ffb6787e8ad80000000000000000000000000000b77d61ea79c7ea8bfa03d3604ce5eabfb95c2ab20000000000000000000000002c57d1cfc6d5f8e4182a56b4cf75421472ebaea4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000001cd4af4a9bf33474c802d31790a195335f7a9ab8000000000000000000000000d676af79742bcaeb4a71cf62b85d5ba2d1deaf86c001a08d03bdca0c1647263ef73d916e949ccc53284c6fa208c3fa4f9ddfe67d9c45dfa055be5793b42f1818716276033eb36420fa4fb4e3efabd0bbb01c489f7d9cd43c","0x02f86c8330182404801b825208948943545177806ed17b9f23f0a21ee5948ecaa7768701e71eeda00c3080c001a05918a7b26059059e3fc130bfeb42707bcdab9efaabad518f02f062a5d79e0ae7a06c3f27be896c38ed49a6a943050680b8bb1544b77a4df75c62ef75b357b27c7b"]`
// preconfRaw := "02f872833018240385e8d4a5100085e8d4a5100082520894deaddeaddeaddeaddeaddeaddeaddeaddeaddead8306942080c001a042696cf1ef039cf23f51b8348c7fcda961727dd6350992b06c6139cf2b66ed18a012252715233c0cb9803bf827942f619b4a6857a9bfb214ef2feba983d1b5ed0e"
//
// // prepare the decoded preconf
// preconf := new(types.Transaction)
// bytes, err := hex.DecodeString(preconfRaw)
// require.NoError(t, err)
// err = preconf.UnmarshalBinary(bytes)
// require.NoError(t, err)
// require.NotNil(t, preconf)
// preconfs := []*types.Transaction{preconf}
//
// transactionsRaw := new([]string)
// err = json.Unmarshal([]byte(raw), transactionsRaw)
// require.NoError(t, err)
//
// // prepare the entire payload
// payloadTransactions := make([]*types.Transaction, 0, 10)
// for _, tx := range *transactionsRaw {
// rawBytes, _ := hex.DecodeString(tx[2:])
// transaction := new(types.Transaction)
// require.NoError(t, err)
// err = transaction.UnmarshalBinary(rawBytes)
// require.NoError(t, err)
// require.NotNil(t, transaction)
// payloadTransactions = append(payloadTransactions, transaction)
// }
//
// t.Logf("Transactions: len %d\n", len(payloadTransactions))
//
// // BUILDER CODE starts here
// if payloadTransactions[0].Hash() != preconf.Hash() {
// t.Fatal("Preconf transaction doesn't match with the first transaction in the payload")
// }
//
// // BOLT: generate merkle tree from payload transactions (we need raw RLP bytes for this)
// rawTxs := make([]bellatrix.Transaction, len(payloadTransactions))
// for i, tx := range payloadTransactions {
// raw, err := tx.MarshalBinary()
// if err != nil {
// log.Error("[BOLT]: could not marshal transaction", "txHash", tx.Hash(), "err", err)
// continue
// }
// rawTxs[i] = bellatrix.Transaction(raw)
// }
//
// bellatrixPayloadTxs := utilbellatrix.ExecutionPayloadTransactions{Transactions: rawTxs}
//
// rootNode, err := bellatrixPayloadTxs.GetTree()
// require.NoError(t, err, "could not get raw txs tree")
//
// t.Logf("rootNode: %x", rootNode.Hash()) // e557527d9e7d97eaf4592637901e02a31c09c27d6076c27970799f418e47deab
//
// // BOLT: calculate merkle proofs for preconfirmed transactions
// preconfirmationsProofs := make([]*common.PreconfirmationWithProof, 0, len(preconfs))
//
// for i, preconf := range preconfs {
// // get the index of the preconfirmed transaction in the block
// preconfIndex := slices.IndexFunc(payloadTransactions, func(tx *types.Transaction) bool { return tx.Hash() == preconf.Hash() })
// if preconfIndex == -1 {
// log.Error(fmt.Sprintf("Preconfirmed transaction %s not found", preconf.Hash()))
// log.Error(fmt.Sprintf("block has %v transactions", len(payloadTransactions)))
// continue
// }
//
// // using our gen index formula: 2 * 2^20 + preconfIndex
// generalizedIndex := int(math.Pow(float64(2), float64(21))) + preconfIndex
//
// t.Logf("[BOLT]: Calculating merkle proof for preconfirmed transaction %s with index %d. Preconf index: %d",
// preconf.Hash(), generalizedIndex, preconfIndex)
//
// timeStart := time.Now()
// proof, err := rootNode.Prove(generalizedIndex)
// require.NoError(t, err, "could not generate proof for preconfirmed transaction")
//
// t.Logf("[BOLT]: Calculated merkle proof for preconf %s in %s", preconf.Hash(), time.Since(timeStart))
// t.Logf("[BOLT]: LEAF: %x, Is leaf nil? %v", proof.Leaf, proof.Leaf == nil)
//
// merkleProof := new(common.SerializedMerkleProof)
// merkleProof.FromFastSszProof(proof)
//
// preconfirmationsProofs = append(preconfirmationsProofs, &common.PreconfirmationWithProof{
// TxHash: phase0.Hash32(preconf.Hash()),
// MerkleProof: merkleProof,
// })
//
// t.Logf("[BOLT]: Added merkle proof for preconfirmed transaction %s", preconfirmationsProofs[i])
// }
//
// t.Logf("[BOLT]: Generated %d merkle proofs for preconfirmed transactions", len(preconfirmationsProofs))
// }

func TestSubscribeProposerConstraints(t *testing.T) {
// ------------ Start Builder setup ------------- //
Expand Down
2 changes: 1 addition & 1 deletion builder/builder/relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func (r *RemoteRelay) SubmitBlockWithProofs(msg *common.VersionedSubmitBlockRequ
} else {

// BOLT: send event to web demo
if len(msg.Proofs) > 0 {
if len(msg.Proofs.TransactionHashes) > 0 {
number, _ := msg.Inner.BlockNumber()
EmitBoltDemoEvent(fmt.Sprintf("sending block %d with proofs to relay (path: %s)", number, "/relay/v1/builder/blocks_with_proofs"))
}
Expand Down
Loading

0 comments on commit 8441feb

Please sign in to comment.