diff --git a/app/featureset/featureset.go b/app/featureset/featureset.go index 4b0eaf5e1..8c5550a26 100644 --- a/app/featureset/featureset.go +++ b/app/featureset/featureset.go @@ -40,6 +40,8 @@ const ( // JSONRequests enables JSON requests for eth2 client. JSONRequests Feature = "json_requests" + + GnosisBlockHotfix Feature = "gnosis_block_hotfix" ) var ( @@ -50,6 +52,7 @@ var ( MockAlpha: statusAlpha, AggSigDBV2: statusAlpha, JSONRequests: statusAlpha, + GnosisBlockHotfix: statusAlpha, // Add all features and there status here. } diff --git a/core/signeddata.go b/core/signeddata.go index 2de6f6483..ed5dc46e9 100644 --- a/core/signeddata.go +++ b/core/signeddata.go @@ -15,11 +15,13 @@ import ( "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/deneb" eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" ssz "github.com/ferranbt/fastssz" "github.com/obolnetwork/charon/app/errors" "github.com/obolnetwork/charon/app/eth2wrap" + "github.com/obolnetwork/charon/app/featureset" "github.com/obolnetwork/charon/eth2util" "github.com/obolnetwork/charon/eth2util/eth2exp" "github.com/obolnetwork/charon/eth2util/signing" @@ -320,6 +322,12 @@ func (p VersionedSignedProposal) MessageRoot() ([32]byte, error) { return p.DenebBlinded.Message.HashTreeRoot() } + if featureset.Enabled(featureset.GnosisBlockHotfix) { + // translate p.Deneb.SignedBlock to its Gnosis associate and return its hash tree root + sbGnosis := deneb.BeaconBlockToGnosis(*p.Deneb.SignedBlock.Message) + return sbGnosis.HashTreeRoot() + } + return p.Deneb.SignedBlock.Message.HashTreeRoot() default: panic("unknown version") // Note this is avoided by using `NewVersionedSignedProposal`. diff --git a/core/signeddata_test.go b/core/signeddata_test.go index 2624a3fd2..7bc54fd05 100644 --- a/core/signeddata_test.go +++ b/core/signeddata_test.go @@ -3,6 +3,7 @@ package core_test import ( + "encoding/hex" "encoding/json" "fmt" "testing" @@ -15,9 +16,11 @@ import ( "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/deneb" eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/stretchr/testify/require" + "github.com/obolnetwork/charon/app/featureset" "github.com/obolnetwork/charon/core" "github.com/obolnetwork/charon/testutil" ) @@ -347,3 +350,64 @@ func TestVersionedSignedProposal(t *testing.T) { }) } } + +func TestGnosisProposals(t *testing.T) { + baseProposal := eth2api.VersionedSignedProposal{ + Version: eth2spec.DataVersionDeneb, + Deneb: testutil.RandomDenebVersionedSignedProposal().Deneb, + } + + rawGnosisProposal, err := core.NewVersionedSignedProposal(&baseProposal) + require.NoError(t, err) + + rawStdProposal, err := core.NewVersionedSignedProposal(&baseProposal) + require.NoError(t, err) + + featureset.EnableForT(t, featureset.GnosisBlockHotfix) + + gnosisProp := core.ParSignedData{ + SignedData: rawGnosisProposal, + ShareIdx: 42, + } + + gnosisRoot, err := gnosisProp.MessageRoot() + require.NoError(t, err) + + featureset.DisableForT(t, featureset.GnosisBlockHotfix) + + stdProp := core.ParSignedData{ + SignedData: rawStdProposal, + ShareIdx: 42, + } + + stdRoot, err := stdProp.MessageRoot() + require.NoError(t, err) + + require.NotEqual(t, stdRoot, gnosisRoot) +} + +func TestGnosisRealBlockHash(t *testing.T) { + const ( + realSszStr = "f476b10000000000dc1c000000000000414c00276374153218243eba1b9c92821d64a83b1b7c5dd3f7c051f7b404e873079d1b1a97f18a191fbdf457f809e958473aed36593981bd990b9ba0103775425400000090a2e40b2745cdbcc797856161cebc85ca517f2c956a28682b4539fc1bd051355b39836bb73502fefe3c9683b6a899c802e3511378915c06dfb4cb5b218261ddc8abe0e5e4b7701272b7cdfb525764f580ad561ead7964bc40c188a609ea40458e289cb0e746320595ef0b1a6f1118b4949f016818c1457beb0d90f3ca06ff55cf05000000000000fc6c3b6e91805bfd2a224716830ab1644c4fdcfaad82aad5a4ac3d0ad33d6d20636861726f6e2f76312e312e302d6465762d38306635613236000000000000008801000088010000880100008805000088050000fffbbffffffff7ffff7ffe7ffffffef6ffffbfbfffeffffbddfffffffffff7ffff9ffffffbfffdffffefffffdfffffbfffbffdfffffdffffafffdffffdfff7bf98cca8f4c0aed716d5f6352e2e4adf58d398d132fadf9352abe53971b8dceba4a7c3ba29e176a4bfd9154736f370e83404bb55af4be453f72c066cbc05e882e29caebc707a0c38573d15c2e0c94e9f65f5c71743a4f24b683a9c5843e06aa2c988050000bc0c0000bc0c0000100000000c0100000802000004030000e4000000f376b100000000000100000000000000414c00276374153218243eba1b9c92821d64a83b1b7c5dd3f7c051f7b404e8736e170b0000000000aad68b53dc54fb14d6ae23decfd7b80b026e044d98f7537eb7a7a599e289d4c96f170b00000000004b57e3fcb8a9098573721e35021ccf5a03a52e5b6d0df29c6410ac3179d98198864379f3d48d26b4567bda0b95be3d4fb4977dc83bb360f117b64c8792ae82697690c5a8e4f66dbff6ea87ef089f9ee000a94c948d638228eb02acbd360bd0018b000496dabd4a0eb6af94a2700b1195d9bbb9c94aa6947a2cd13f7e460b15b7ffffffffffffffffdffeffffffefeffffffcffffffffff03e4000000f376b100000000000000000000000000414c00276374153218243eba1b9c92821d64a83b1b7c5dd3f7c051f7b404e8736e170b0000000000aad68b53dc54fb14d6ae23decfd7b80b026e044d98f7537eb7a7a599e289d4c96f170b00000000004b57e3fcb8a9098573721e35021ccf5a03a52e5b6d0df29c6410ac3179d98198a8823ae587250ebb681e5041b0e98177da564588e681fc9f03413dede12cb736edb0894d92d8acd453610a44faef61820884f08b95825263b6eb85ae3a1a9b7e372f24886f14f51f960a8ed7701621bc391518a771d82479159600dcf5fb472effffffffdffffffffeffffffffbdffffbf7fffffdfffff01e4000000f176b1000000000001000000000000002ab5a55abbffb1d54156aedc8f532ddc8e20b52ad418d274976c84bed8c6dfb96e170b0000000000aad68b53dc54fb14d6ae23decfd7b80b026e044d98f7537eb7a7a599e289d4c96f170b00000000004b57e3fcb8a9098573721e35021ccf5a03a52e5b6d0df29c6410ac3179d9819892abafc310b9960acd95315361d9fbf7d6dcace2525f614ac6202d34e09639b5fa654a80ad33cf1587faac32ee8799870475e38f3f34a7e8916692f2957c3bdc02e29b00c3bc236a690677165f015435f4995aa3fc24e8d4d757b6b410a694c3000000000000000000000000000000000000000000001002e4000000f176b1000000000000000000000000002ab5a55abbffb1d54156aedc8f532ddc8e20b52ad418d274976c84bed8c6dfb96e170b0000000000aad68b53dc54fb14d6ae23decfd7b80b026e044d98f7537eb7a7a599e289d4c96f170b00000000004b57e3fcb8a9098573721e35021ccf5a03a52e5b6d0df29c6410ac3179d981988accfe8cd3246977e00443e4096ee201b57f3ebc03df78ce067ef40a994baf40f020ffb197c339773f807cddea4f9c51036909facf3fcf763998ae703c8c70aacc6519948bd20283800f199b08ff6005b6c4ec096bd3aef1cb93efc17f0724b4000000000000000000000000000000000000000004000001b1b6d67608054e32ca56184f8bf612f9c0e8df9f99fc7607ff1fe865e80f04317ce7390c41ce3416c4a0a297761c71763d89ca3baa3f345a5c48d0e9470c650b41e38dfdae05db0df098fae94a61cd1fca5b6fdac426de6f735cfb1799cbc9729fa60a19de90b88201b1ba442cc5624a17f6a3590000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000026f600f4172341b485e1fb406f5334b3d4887d896ca54fd71ba2590c752574ef3905ac000000000062690401000000003ddd0100000000001041bb66000000001002000007000000000000000000000000000000000000000000000000000000000000008eb2a7234864cd0d17fe81e4c229130ee883dce5b577801526e355d4a44a397b1a020000d4050000000000000000000000000000000000004e65746865726d696e6408000000e101000002f901d58227d8825e9784b2d05e0084b2d05e00831632a8948448e15d0e706c0298deca99f0b4744030e59d7d80b90164e7a2c01f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000118000500000735a05d7e98453b1abcedec7918072d3d6f5ec20000000000000d3ec6755144d60548f3dd420f47cf48dae553bbf0423f5929bee6a59661d6ccc9c4eb751048009ce11b0007a120030200aa36a727d869f5590300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000187290cd402054b514751d3cfb037c8ee0eda175f7c6b19c0b0dd529dff22054800000000000000000000000000000000000000000000000000000000000000012cda73507147b818a330a53afea6536c40ea9fb3e20107a1147fdb1e798417d90000000000000000c001a019a3a3e71b2e3418e753bbee132a4dbfebf6fe54c09a5efafcaafd48a797f2e6a044d24ef0e9d489da2f1cf5969a2a639506f133e8a35dc7b546fe84977c6473a202f901d58227d8825e9884b2d05e0084b2d05e00831632a8948448e15d0e706c0298deca99f0b4744030e59d7d80b90164e7a2c01f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000118000500000735a05d7e98453b1abcedec7918072d3d6f5ec20000000000000d54c6755144d60548f3dd420f47cf48dae553bbf0423f5929bee6a59661d6ccc9c4eb751048009ce11b0007a120030200aa36a727d869f55903000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001ca21552ef9a14f965d7fd033812e635db7b1f5d5a25f42d5749d804653ac80dd0000000000000000000000000000000000000000000000000000000000000001bc4cd07038c77b7e752abf26ceaa64d2d9c71d05b6436a95bc2b3f5a2173eb920000000000000000c001a075bd587dcc1b039a8ad587050af69ec56714a570d7cb90f1de673cff7d2f2151a0540002cf0799372453b7d752fe235bd5d83c30cf8fdb8eb3fefd5060e093b2825e27050300000000960f000000000000cc4e00a72d871d6c328bcfe9025ad93d0a26df5173f50e00000000005f27050300000000970f000000000000cc4e00a72d871d6c328bcfe9025ad93d0a26df5196470f00000000006027050300000000980f000000000000cc4e00a72d871d6c328bcfe9025ad93d0a26df5196470f00000000006127050300000000990f000000000000cc4e00a72d871d6c328bcfe9025ad93d0a26df51b01b0f000000000062270503000000009a0f000000000000cc4e00a72d871d6c328bcfe9025ad93d0a26df51d4293c000000000063270503000000009b0f000000000000cc4e00a72d871d6c328bcfe9025ad93d0a26df51f51e0f000000000064270503000000009d0f000000000000cc4e00a72d871d6c328bcfe9025ad93d0a26df51b6f20e000000000065270503000000009e0f000000000000cc4e00a72d871d6c328bcfe9025ad93d0a26df51a283150000000000" + expectedGnosisHashStr = "9ddaf2f91ad6b426603286c98aa71a659b13475c4e71c9b5603b86528a072137" + expectedStdHashStr = "bdf303daf3b3f1735460c0ee0de2646a39247daadf091d3cfc7f7cb70c696426" + ) + realSsz, err := hex.DecodeString(realSszStr) + require.NoError(t, err) + + m := &deneb.GnosisBeaconBlock{} + err = m.UnmarshalSSZ(realSsz) + require.NoError(t, err) + + realHash, err := m.HashTreeRoot() + require.NoError(t, err) + require.Equal(t, expectedGnosisHashStr, hex.EncodeToString(realHash[:])) + + mStd := &deneb.BeaconBlock{} + err = mStd.UnmarshalSSZ(realSsz) + require.NoError(t, err) + + realHashStd, err := mStd.HashTreeRoot() + require.NoError(t, err) + require.Equal(t, expectedStdHashStr, hex.EncodeToString(realHashStd[:])) +} diff --git a/core/validatorapi/validatorapi_test.go b/core/validatorapi/validatorapi_test.go index a6199366e..1bea36d83 100644 --- a/core/validatorapi/validatorapi_test.go +++ b/core/validatorapi/validatorapi_test.go @@ -20,6 +20,7 @@ import ( "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/deneb" eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/prysmaticlabs/go-bitfield" "github.com/stretchr/testify/require" @@ -27,6 +28,7 @@ import ( "github.com/obolnetwork/charon/app/errors" "github.com/obolnetwork/charon/app/eth2wrap" + "github.com/obolnetwork/charon/app/featureset" "github.com/obolnetwork/charon/core" "github.com/obolnetwork/charon/core/validatorapi" "github.com/obolnetwork/charon/eth2util" @@ -491,6 +493,96 @@ func TestComponent_SubmitProposal(t *testing.T) { require.NoError(t, err) } +func TestComponent_SubmitProposal_Gnosis(t *testing.T) { + ctx := context.Background() + + featureset.EnableForT(t, featureset.GnosisBlockHotfix) + defer featureset.DisableForT(t, featureset.GnosisBlockHotfix) + + // Create keys (just use normal keys, not split tbls) + secret, err := tbls.GenerateSecretKey() + require.NoError(t, err) + + pubkey, err := tbls.SecretToPublicKey(secret) + require.NoError(t, err) + + const ( + vIdx = 1 + shareIdx = 1 + slot = 123 + epoch = eth2p0.Epoch(3) + ) + + // Convert pubkey + corePubKey, err := core.PubKeyFromBytes(pubkey[:]) + require.NoError(t, err) + allPubSharesByKey := map[core.PubKey]map[int]tbls.PublicKey{corePubKey: {shareIdx: pubkey}} // Maps self to self since not tbls + + // Configure beacon mock + bmock, err := beaconmock.New() + require.NoError(t, err) + + // Construct the validator api component + vapi, err := validatorapi.NewComponent(bmock, allPubSharesByKey, shareIdx, nil, testutil.BuilderFalse, nil) + require.NoError(t, err) + + // Prepare unsigned beacon block + msg := []byte("randao reveal") + sig, err := tbls.Sign(secret, msg) + require.NoError(t, err) + + randao := eth2p0.BLSSignature(sig) + unsignedBlock := ð2spec.VersionedBeaconBlock{ + Version: eth2spec.DataVersionDeneb, + Deneb: testutil.RandomDenebBeaconBlock(), + } + unsignedBlock.Deneb.Body.RANDAOReveal = randao + unsignedBlock.Deneb.Slot = slot + unsignedBlock.Deneb.ProposerIndex = vIdx + + vapi.RegisterGetDutyDefinition(func(ctx context.Context, duty core.Duty) (core.DutyDefinitionSet, error) { + return core.DutyDefinitionSet{corePubKey: nil}, nil + }) + + gnosisBlock := deneb.BeaconBlockToGnosis(*unsignedBlock.Deneb) + // Sign beacon block + sigRoot, err := gnosisBlock.HashTreeRoot() + require.NoError(t, err) + + domain, err := signing.GetDomain(ctx, bmock, signing.DomainBeaconProposer, epoch) + require.NoError(t, err) + + sigData, err := (ð2p0.SigningData{ObjectRoot: sigRoot, Domain: domain}).HashTreeRoot() + require.NoError(t, err) + + s, err := tbls.Sign(secret, sigData[:]) + require.NoError(t, err) + + signedBlock := ð2api.VersionedSignedProposal{ + Version: unsignedBlock.Version, + Deneb: ð2deneb.SignedBlockContents{ + SignedBlock: &deneb.SignedBeaconBlock{ + Message: unsignedBlock.Deneb, + Signature: eth2p0.BLSSignature(s), + }, + }, + } + + // Register subscriber + vapi.Subscribe(func(ctx context.Context, duty core.Duty, set core.ParSignedDataSet) error { + block, ok := set[corePubKey].SignedData.(core.VersionedSignedProposal) + require.True(t, ok) + require.Equal(t, *signedBlock, block.VersionedSignedProposal) + + return nil + }) + + err = vapi.SubmitProposal(ctx, ð2api.SubmitProposalOpts{ + Proposal: signedBlock, + }) + require.NoError(t, err) +} + func TestComponent_SubmitProposalInvalidSignature(t *testing.T) { ctx := context.Background() diff --git a/go.mod b/go.mod index b8c70fb92..947c439fe 100644 --- a/go.mod +++ b/go.mod @@ -205,3 +205,7 @@ require ( // We're replacing kryptology with our own fork, which fixes dependencies' security vulnerabilities. replace github.com/coinbase/kryptology => github.com/ObolNetwork/kryptology v0.0.0-20231016091344-eed023b6cac8 + +// We're replacing go-eth2-client with a branch off our fork, at version v0.21.10. +// This is needed to ensure Gnosis compatibility. +replace github.com/attestantio/go-eth2-client => github.com/ObolNetwork/go-eth2-client v0.21.11-0.20240822135044-f0a5b21e02c6 diff --git a/go.sum b/go.sum index b119c8bec..8d31047ec 100644 --- a/go.sum +++ b/go.sum @@ -24,13 +24,13 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ObolNetwork/go-eth2-client v0.21.11-0.20240822135044-f0a5b21e02c6 h1:VEBrga7Dn5SwvJQEG3i2K7IAUQQvEmUulWxXoBDimnM= +github.com/ObolNetwork/go-eth2-client v0.21.11-0.20240822135044-f0a5b21e02c6/go.mod h1:d7ZPNrMX8jLfIgML5u7QZxFo2AukLM+5m08iMaLdqb8= github.com/ObolNetwork/kryptology v0.0.0-20231016091344-eed023b6cac8 h1:IXoKQKGzebwtIzKADtZyAjL3MIr0m3zQFxlSxxWIdCU= github.com/ObolNetwork/kryptology v0.0.0-20231016091344-eed023b6cac8/go.mod h1:qcn33Qgj0WVLH1nuXqPt8JHY+yZabhyKxI5dHRk0fbo= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -github.com/attestantio/go-eth2-client v0.21.10 h1:1DWn42WKjk8mR8jKkjbaDCGNMVnh2IfAWRUmt7iemRo= -github.com/attestantio/go-eth2-client v0.21.10/go.mod h1:d7ZPNrMX8jLfIgML5u7QZxFo2AukLM+5m08iMaLdqb8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=