From abf8be83afd9de2a9be350da6adbe86b76be343f Mon Sep 17 00:00:00 2001 From: Arnau Date: Tue, 30 Jul 2024 14:59:43 +0200 Subject: [PATCH] pass test vectors --- bridgesync/bridgesync.go | 4 +- bridgesync/processor.go | 116 ++++++-- bridgesync/processor_test.go | 43 ++- bridgesync/testvectors/claim-vectors.json | 306 ++++++++++++++++++++++ bridgesync/testvectors/leaf-vectors.json | 38 +++ bridgesync/testvectors/root-vectors.json | 67 +++++ bridgesync/tree.go | 292 +++++++++++++++++++-- bridgesync/tree_test.go | 141 ++++++++++ 8 files changed, 960 insertions(+), 47 deletions(-) create mode 100644 bridgesync/testvectors/claim-vectors.json create mode 100644 bridgesync/testvectors/leaf-vectors.json create mode 100644 bridgesync/testvectors/root-vectors.json create mode 100644 bridgesync/tree_test.go diff --git a/bridgesync/bridgesync.go b/bridgesync/bridgesync.go index 2a95acb9..c9dceb77 100644 --- a/bridgesync/bridgesync.go +++ b/bridgesync/bridgesync.go @@ -86,7 +86,7 @@ func new( initialBlock uint64, dbPrefix, reorgDetectorID string, ) (*LocalBridgeSync, error) { - processor, err := newProcessor(dbPath, dbPrefix) + processor, err := newProcessor(ctx, dbPath, dbPrefix) if err != nil { return nil, err } @@ -132,3 +132,5 @@ func new( func (s *LocalBridgeSync) Start(ctx context.Context) { s.driver.Sync(ctx) } + +// TODO: expose methods from the processor for consumers diff --git a/bridgesync/processor.go b/bridgesync/processor.go index 30738d01..49c1c1b4 100644 --- a/bridgesync/processor.go +++ b/bridgesync/processor.go @@ -2,13 +2,16 @@ package bridgesync import ( "context" + "encoding/binary" "encoding/json" "errors" + "log" "math/big" - "github.com/0xPolygon/cdk/common" + dbCommon "github.com/0xPolygon/cdk/common" "github.com/0xPolygon/cdk/sync" - ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common" + "github.com/iden3/go-iden3-crypto/keccak256" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/kv/mdbx" ) @@ -16,29 +19,56 @@ import ( const ( eventsTableSufix = "-events" lastBlockTableSufix = "-lastBlock" + rootTableSufix = "-root" + rhtTableSufix = "-rht" ) var ( ErrBlockNotProcessed = errors.New("given block(s) have not been processed yet") + ErrNotFound = errors.New("not found") lastBlokcKey = []byte("lb") ) type Bridge struct { LeafType uint8 OriginNetwork uint32 - OriginAddress ethCommon.Address + OriginAddress common.Address DestinationNetwork uint32 - DestinationAddress ethCommon.Address + DestinationAddress common.Address Amount *big.Int Metadata []byte DepositCount uint32 } +func (b *Bridge) Hash() common.Hash { + origNet := make([]byte, 4) //nolint:gomnd + binary.BigEndian.PutUint32(origNet, uint32(b.OriginNetwork)) + destNet := make([]byte, 4) //nolint:gomnd + binary.BigEndian.PutUint32(destNet, uint32(b.DestinationNetwork)) + + metaHash := keccak256.Hash(b.Metadata) + hash := common.Hash{} + var buf [32]byte //nolint:gomnd + copy( + hash[:], + keccak256.Hash( + []byte{b.LeafType}, + origNet, + b.OriginAddress[:], + destNet, + b.DestinationAddress[:], + b.Amount.FillBytes(buf[:]), + metaHash, + ), + ) + return hash +} + type Claim struct { GlobalIndex *big.Int OriginNetwork uint32 - OriginAddress ethCommon.Address - DestinationAddress ethCommon.Address + OriginAddress common.Address + DestinationAddress common.Address Amount *big.Int } @@ -51,27 +81,38 @@ type processor struct { db kv.RwDB eventsTable string lastBlockTable string + tree *tree } -func newProcessor(dbPath, dbPrefix string) (*processor, error) { +func newProcessor(ctx context.Context, dbPath, dbPrefix string) (*processor, error) { eventsTable := dbPrefix + eventsTableSufix lastBlockTable := dbPrefix + lastBlockTableSufix + rootTable := dbPrefix + rootTableSufix + rhtTable := dbPrefix + rhtTableSufix db, err := mdbx.NewMDBX(nil). Path(dbPath). WithTableCfg(func(defaultBuckets kv.TableCfg) kv.TableCfg { return kv.TableCfg{ eventsTable: {}, lastBlockTable: {}, + rootTable: {}, + rhtTable: {}, } }). Open() if err != nil { return nil, err } + + tree, err := newTree(ctx, rhtTable, rootTable, db) + if err != nil { + return nil, err + } return &processor{ db: db, eventsTable: eventsTable, lastBlockTable: lastBlockTable, + tree: tree, }, nil } @@ -100,11 +141,11 @@ func (p *processor) GetClaimsAndBridges( } defer c.Close() - for k, v, err := c.Seek(common.Uint64To2Bytes(fromBlock)); k != nil; k, v, err = c.Next() { + for k, v, err := c.Seek(dbCommon.Uint64To2Bytes(fromBlock)); k != nil; k, v, err = c.Next() { if err != nil { return nil, err } - if common.BytesToUint64(k) > toBlock { + if dbCommon.BytesToUint64(k) > toBlock { break } blockEvents := []Event{} @@ -133,7 +174,7 @@ func (p *processor) getLastProcessedBlockWithTx(tx kv.Tx) (uint64, error) { } else if blockNumBytes == nil { return 0, nil } else { - return common.BytesToUint64(blockNumBytes), nil + return dbCommon.BytesToUint64(blockNumBytes), nil } } @@ -147,8 +188,9 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { return err } defer c.Close() - firstKey := common.Uint64To2Bytes(firstReorgedBlock) - for k, _, err := c.Seek(firstKey); k != nil; k, _, err = c.Next() { + firstKey := dbCommon.Uint64To2Bytes(firstReorgedBlock) + firstDepositCountReorged := int64(-1) + for k, v, err := c.Seek(firstKey); k != nil; k, _, err = c.Next() { if err != nil { tx.Rollback() return err @@ -157,42 +199,84 @@ func (p *processor) Reorg(firstReorgedBlock uint64) error { tx.Rollback() return err } + if firstDepositCountReorged == -1 { + events := []Event{} + if err := json.Unmarshal(v, &events); err != nil { + tx.Rollback() + return err + } + for _, event := range events { + if event.Bridge != nil { + firstDepositCountReorged = int64(event.Bridge.DepositCount) + break + } + } + } } if err := p.updateLastProcessedBlock(tx, firstReorgedBlock-1); err != nil { tx.Rollback() return err } + if firstDepositCountReorged != -1 { + lastValidDepositCount := uint32(firstDepositCountReorged) - 1 + if err := p.tree.reorg(tx, lastValidDepositCount); err != nil { + tx.Rollback() + return err + } + } return tx.Commit() } func (p *processor) ProcessBlock(block sync.Block) error { - tx, err := p.db.BeginRw(context.Background()) + ctx := context.Background() + tx, err := p.db.BeginRw(ctx) if err != nil { return err } + bridges := []Bridge{} if len(block.Events) > 0 { events := []Event{} for _, e := range block.Events { - events = append(events, e.(Event)) + event := e.(Event) + events = append(events, event) + if event.Bridge != nil { + bridges = append(bridges, *event.Bridge) + } } value, err := json.Marshal(events) if err != nil { tx.Rollback() return err } - if err := tx.Put(p.eventsTable, common.Uint64To2Bytes(block.Num), value); err != nil { + if err := tx.Put(p.eventsTable, dbCommon.Uint64To2Bytes(block.Num), value); err != nil { tx.Rollback() return err } } + if err := p.updateLastProcessedBlock(tx, block.Num); err != nil { tx.Rollback() return err } + + for i, bridge := range bridges { + if err := p.tree.addLeaf(tx, bridge.DepositCount, bridge.Hash()); err != nil { + if i != 0 { + tx.Rollback() + if err2 := p.tree.initLastLeftCacheAndLastDepositCount(ctx); err2 != nil { + log.Fatalf( + "after failing to add a leaf to the tree with error: %v, error initializing the cache with error: %v", + err, err2, + ) + } + return err + } + } + } return tx.Commit() } func (p *processor) updateLastProcessedBlock(tx kv.RwTx, blockNum uint64) error { - blockNumBytes := common.Uint64To2Bytes(blockNum) + blockNumBytes := dbCommon.Uint64To2Bytes(blockNum) return tx.Put(p.lastBlockTable, lastBlokcKey, blockNumBytes) } diff --git a/bridgesync/processor_test.go b/bridgesync/processor_test.go index 60bc6b19..24e65e5c 100644 --- a/bridgesync/processor_test.go +++ b/bridgesync/processor_test.go @@ -2,8 +2,10 @@ package bridgesync import ( "context" + "encoding/json" "fmt" "math/big" + "os" "slices" "testing" @@ -14,7 +16,7 @@ import ( func TestProceessor(t *testing.T) { path := t.TempDir() - p, err := newProcessor(path, "foo") + p, err := newProcessor(context.Background(), path, "foo") require.NoError(t, err) actions := []processAction{ // processed: ~ @@ -422,3 +424,42 @@ func eventsToBridgeEvents(events []interface{}) []Event { } return bridgeEvents } + +// DepositVectorRaw represents the deposit vector +type DepositVectorRaw struct { + OriginalNetwork uint32 `json:"originNetwork"` + TokenAddress string `json:"tokenAddress"` + Amount string `json:"amount"` + DestinationNetwork uint32 `json:"destinationNetwork"` + DestinationAddress string `json:"destinationAddress"` + ExpectedHash string `json:"leafValue"` + CurrentHash string `json:"currentLeafValue"` + Metadata string `json:"metadata"` +} + +func TestHashBridge(t *testing.T) { + data, err := os.ReadFile("testvectors/leaf-vectors.json") + require.NoError(t, err) + + var leafVectors []DepositVectorRaw + err = json.Unmarshal(data, &leafVectors) + require.NoError(t, err) + + for ti, testVector := range leafVectors { + t.Run(fmt.Sprintf("Test vector %d", ti), func(t *testing.T) { + amount, err := big.NewInt(0).SetString(testVector.Amount, 0) + require.True(t, err) + + bridge := Bridge{ + OriginNetwork: testVector.OriginalNetwork, + OriginAddress: common.HexToAddress(testVector.TokenAddress), + Amount: amount, + DestinationNetwork: testVector.DestinationNetwork, + DestinationAddress: common.HexToAddress(testVector.DestinationAddress), + DepositCount: uint32(ti + 1), + Metadata: common.FromHex(testVector.Metadata), + } + require.Equal(t, common.HexToHash(testVector.ExpectedHash), bridge.Hash()) + }) + } +} diff --git a/bridgesync/testvectors/claim-vectors.json b/bridgesync/testvectors/claim-vectors.json new file mode 100644 index 00000000..4778e9e4 --- /dev/null +++ b/bridgesync/testvectors/claim-vectors.json @@ -0,0 +1,306 @@ +[ + { + "leafs": [ + { + "originNetwork": 0, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x", + "leafValue": "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5" + }, + { + "originNetwork": 1, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 0, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345670", + "leafValue": "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f" + }, + { + "originNetwork": 0, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345678", + "leafValue": "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d" + }, + { + "originNetwork": 10, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x01", + "destinationNetwork": 4, + "destinationAddress": "0x0000000000000000000000000000000000000000", + "metadata": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922661234e51aad88F6F4ce6aB8827279cffFb92266", + "leafValue": "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242" + } + ], + "index": 0, + "proof": [ + "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f", + "0xb48c8301099f75206bc93b1512c7b3855b60b4f8cbaedf8679a184d1d450a4f1", + "0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30", + "0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85", + "0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344", + "0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d", + "0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968", + "0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83", + "0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af", + "0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0", + "0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5", + "0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892", + "0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c", + "0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb", + "0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc", + "0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2", + "0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f", + "0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a", + "0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0", + "0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", + "0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2", + "0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9", + "0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377", + "0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652", + "0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef", + "0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d", + "0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0", + "0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e", + "0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e", + "0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322", + "0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735", + "0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9" + ], + "root": "0x42d3339fe8eb57770953423f20a029e778a707e8d58aaf110b40d5eb4dd25721" + }, + { + "leafs": [ + { + "originNetwork": 0, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x", + "leafValue": "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5" + }, + { + "originNetwork": 1, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 0, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345670", + "leafValue": "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f" + }, + { + "originNetwork": 0, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345678", + "leafValue": "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d" + }, + { + "originNetwork": 10, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x01", + "destinationNetwork": 4, + "destinationAddress": "0x0000000000000000000000000000000000000000", + "metadata": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922661234e51aad88F6F4ce6aB8827279cffFb92266", + "leafValue": "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242" + } + ], + "index": 1, + "proof": [ + "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5", + "0xb48c8301099f75206bc93b1512c7b3855b60b4f8cbaedf8679a184d1d450a4f1", + "0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30", + "0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85", + "0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344", + "0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d", + "0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968", + "0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83", + "0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af", + "0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0", + "0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5", + "0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892", + "0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c", + "0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb", + "0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc", + "0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2", + "0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f", + "0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a", + "0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0", + "0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", + "0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2", + "0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9", + "0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377", + "0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652", + "0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef", + "0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d", + "0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0", + "0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e", + "0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e", + "0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322", + "0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735", + "0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9" + ], + "root": "0x42d3339fe8eb57770953423f20a029e778a707e8d58aaf110b40d5eb4dd25721" + }, + { + "leafs": [ + { + "originNetwork": 0, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x", + "leafValue": "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5" + }, + { + "originNetwork": 1, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 0, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345670", + "leafValue": "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f" + }, + { + "originNetwork": 0, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345678", + "leafValue": "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d" + }, + { + "originNetwork": 10, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x01", + "destinationNetwork": 4, + "destinationAddress": "0x0000000000000000000000000000000000000000", + "metadata": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922661234e51aad88F6F4ce6aB8827279cffFb92266", + "leafValue": "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242" + } + ], + "index": 2, + "proof": [ + "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242", + "0x653142d4a4d6f7985a3f33cad31e011dbee8909846b34c38c7b235ca08828521", + "0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30", + "0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85", + "0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344", + "0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d", + "0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968", + "0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83", + "0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af", + "0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0", + "0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5", + "0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892", + "0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c", + "0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb", + "0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc", + "0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2", + "0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f", + "0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a", + "0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0", + "0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", + "0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2", + "0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9", + "0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377", + "0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652", + "0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef", + "0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d", + "0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0", + "0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e", + "0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e", + "0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322", + "0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735", + "0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9" + ], + "root": "0x42d3339fe8eb57770953423f20a029e778a707e8d58aaf110b40d5eb4dd25721" + }, + { + "leafs": [ + { + "originNetwork": 0, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x", + "leafValue": "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5" + }, + { + "originNetwork": 1, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 0, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345670", + "leafValue": "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f" + }, + { + "originNetwork": 0, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345678", + "leafValue": "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d" + }, + { + "originNetwork": 10, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x01", + "destinationNetwork": 4, + "destinationAddress": "0x0000000000000000000000000000000000000000", + "metadata": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922661234e51aad88F6F4ce6aB8827279cffFb92266", + "leafValue": "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242" + } + ], + "index": 3, + "proof": [ + "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d", + "0x653142d4a4d6f7985a3f33cad31e011dbee8909846b34c38c7b235ca08828521", + "0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30", + "0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85", + "0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344", + "0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d", + "0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968", + "0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83", + "0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af", + "0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0", + "0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5", + "0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892", + "0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c", + "0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb", + "0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc", + "0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2", + "0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f", + "0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a", + "0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0", + "0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", + "0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2", + "0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9", + "0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377", + "0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652", + "0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef", + "0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d", + "0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0", + "0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e", + "0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e", + "0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322", + "0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735", + "0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9" + ], + "root": "0x42d3339fe8eb57770953423f20a029e778a707e8d58aaf110b40d5eb4dd25721" + } +] \ No newline at end of file diff --git a/bridgesync/testvectors/leaf-vectors.json b/bridgesync/testvectors/leaf-vectors.json new file mode 100644 index 00000000..a0be0895 --- /dev/null +++ b/bridgesync/testvectors/leaf-vectors.json @@ -0,0 +1,38 @@ +[ + { + "originNetwork": 0, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x", + "leafValue": "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5" + }, + { + "originNetwork": 1, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 0, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345670", + "leafValue": "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f" + }, + { + "originNetwork": 0, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "metadata": "0x12345678", + "leafValue": "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d" + }, + { + "originNetwork": 10, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x01", + "destinationNetwork": 4, + "destinationAddress": "0x0000000000000000000000000000000000000000", + "metadata": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922661234e51aad88F6F4ce6aB8827279cffFb92266", + "leafValue": "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242" + } +] \ No newline at end of file diff --git a/bridgesync/testvectors/root-vectors.json b/bridgesync/testvectors/root-vectors.json new file mode 100644 index 00000000..b1c6929c --- /dev/null +++ b/bridgesync/testvectors/root-vectors.json @@ -0,0 +1,67 @@ +[ + { + "previousLeafsValues": [], + "currentRoot": "0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757", + "newLeaf": { + "originNetwork": 0, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "currentLeafValue": "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5", + "metadata": "0x" + }, + "newRoot": "0xbf7ddbb59aa018a4c74e061f5172973ff09e4cb7f58405af117fc521f1ca46aa" + }, + { + "previousLeafsValues": [ + "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5" + ], + "currentRoot": "0xbf7ddbb59aa018a4c74e061f5172973ff09e4cb7f58405af117fc521f1ca46aa", + "newLeaf": { + "originNetwork": 1, + "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 0, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "currentLeafValue": "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f", + "metadata": "0x12345670" + }, + "newRoot": "0xa7042a3ce14f384bbff63f1cee6ee5579193c2d7002e0034854963322cda6128" + }, + { + "previousLeafsValues": [ + "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5", + "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f" + ], + "currentRoot": "0xa7042a3ce14f384bbff63f1cee6ee5579193c2d7002e0034854963322cda6128", + "newLeaf": { + "originNetwork": 0, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x8ac7230489e80000", + "destinationNetwork": 1, + "destinationAddress": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "currentLeafValue": "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d", + "metadata": "0x12345678" + }, + "newRoot": "0x88e652896cb1de5962a0173a222059f51e6b943a2ba6dfc9acbff051ceb1abb5" + }, + { + "previousLeafsValues": [ + "0xa4bfa0908dc7b06d98da4309f859023d6947561bc19bc00d77f763dea1a0b9f5", + "0x315fee1aa202bf4a6bd0fde560c89be90b6e6e2aaf92dc5e8d118209abc3410f", + "0xb598ce65aa15c08dda126a2985ba54f0559eaac562bb43ba430c7344261fbc5d" + ], + "currentRoot": "0x88e652896cb1de5962a0173a222059f51e6b943a2ba6dfc9acbff051ceb1abb5", + "newLeaf": { + "originNetwork": 10, + "tokenAddress": "0x0000000000000000000000000000000000000000", + "amount": "0x01", + "destinationNetwork": 4, + "destinationAddress": "0x0000000000000000000000000000000000000000", + "currentLeafValue": "0xe6585bdf74b6a46b9ede8b1b877e1232fb79ee93106c4db8ffd49cf1685bf242", + "metadata": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922661234e51aad88F6F4ce6aB8827279cffFb92266" + }, + "newRoot": "0x42d3339fe8eb57770953423f20a029e778a707e8d58aaf110b40d5eb4dd25721" + } +] \ No newline at end of file diff --git a/bridgesync/tree.go b/bridgesync/tree.go index 0dbf7d84..0e6e9620 100644 --- a/bridgesync/tree.go +++ b/bridgesync/tree.go @@ -1,19 +1,28 @@ package bridgesync import ( - "errors" + "context" "fmt" + "math" - "github.com/ledgerwatch/erigon-lib/common" + dbCommon "github.com/0xPolygon/cdk/common" + "github.com/ethereum/go-ethereum/common" "github.com/ledgerwatch/erigon-lib/kv" "golang.org/x/crypto/sha3" ) +const ( + defaultHeight uint8 = 32 +) + type tree struct { - db kv.RwDB - lastIndex int64 - height uint8 - rightPathCache []common.Hash + db kv.RwDB + rhtTable string + rootTable string + height uint8 + lastDepositCount int64 + lastLeftCache []common.Hash + zeroHashes []common.Hash } type treeNode struct { @@ -30,42 +39,122 @@ func (n *treeNode) hash() common.Hash { return hash } -func newTree() (*tree, error) { - // TODO: init lastIndex & rightPathCache - return &tree{}, errors.New("not implemented") +func (n *treeNode) MarshalBinary() ([]byte, error) { + return append(n.left[:], n.right[:]...), nil +} + +func (n *treeNode) UnmarshalBinary(data []byte) error { + if len(data) != 64 { + return fmt.Errorf("expected len %d, actual len %d", 64, len(data)) + } + n.left = common.Hash(data[:32]) + n.right = common.Hash(data[32:]) + return nil } -func (t *tree) addLeaf(index uint, hash common.Hash) error { - if int64(index) != t.lastIndex+1 { - return fmt.Errorf("mismatched index. Expected: %d, actual: %d", t.lastIndex+1, index) +func newTree(ctx context.Context, rhtTable, rootTable string, db kv.RwDB) (*tree, error) { + t := &tree{ + rhtTable: rhtTable, + rootTable: rootTable, + db: db, + height: defaultHeight, + zeroHashes: generateZeroHashes(defaultHeight), + } + + if err := t.initLastLeftCacheAndLastDepositCount(ctx); err != nil { + return nil, err + } + + return t, nil +} + +// getProof returns the merkle proof for a given deposit count and root. +func (t *tree) getProof(ctx context.Context, depositCount uint32, root common.Hash) ([]common.Hash, error) { + tx, err := t.db.BeginRw(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + siblings := make([]common.Hash, int(t.height)) + + currentNodeHash := root + // It starts in height-1 because 0 is the level of the leafs + for h := int(t.height - 1); h >= 0; h-- { + currentNode, err := t.getRHTNode(tx, currentNodeHash) + if err != nil { + return nil, fmt.Errorf( + "height: %d, currentNode: %s, error: %v", + h, currentNodeHash.Hex(), err, + ) + } + /* + * Root (level h=3 => height=4) + * / \ + * O5 O6 (level h=2) + * / \ / \ + * O1 O2 O3 O4 (level h=1) + * /\ /\ /\ /\ + * 0 1 2 3 4 5 6 7 Leafs (level h=0) + * Example 1: + * Choose index = 3 => 011 binary + * Assuming we are in level 1 => h=1; 1< 011&010=010 which is higher than 0 so we need the left sibling (O1) + * Example 2: + * Choose index = 4 => 100 binary + * Assuming we are in level 1 => h=1; 1< 100&010=000 which is not higher than 0 so we need the right sibling (O4) + * Example 3: + * Choose index = 4 => 100 binary + * Assuming we are in level 2 => h=2; 1< 100&100=100 which is higher than 0 so we need the left sibling (O5) + */ + if depositCount&(1< 0 { + siblings = append(siblings, currentNode.left) + currentNodeHash = currentNode.right + } else { + siblings = append(siblings, currentNode.right) + currentNodeHash = currentNode.left + } + } + + // Reverse siblings to go from leafs to root + for i, j := 0, len(siblings)-1; i < j; i, j = i+1, j-1 { + siblings[i], siblings[j] = siblings[j], siblings[i] } + return siblings, nil +} + +func (t *tree) addLeaf(tx kv.RwTx, depositCount uint32, hash common.Hash) error { + // Sanity check + if int64(depositCount) != t.lastDepositCount+1 { + return fmt.Errorf( + "mismatched index. Expected: %d, actual: %d", + t.lastDepositCount+1, depositCount, + ) + } + + // Calculate new tree nodes currentChildHash := hash - leftIsFilled := true newNodes := []treeNode{} for h := uint8(0); h < t.height; h++ { var parent treeNode - if index&(1< 0 { + if depositCount&(1< 0 { // Add child to the right - var child common.Hash - copy(child[:], currentChildHash[:]) parent = treeNode{ - left: t.rightPathCache[h], - right: child, + left: t.lastLeftCache[h], + right: currentChildHash, } } else { // Add child to the left - if leftIsFilled { - // if at this level the left is filled, it means that the new node will be in the right path - copy(t.rightPathCache[h][:], currentChildHash[:]) - leftIsFilled = false - } - var child common.Hash - copy(child[:], currentChildHash[:]) parent = treeNode{ - left: child, - right: common.Hash{}, + left: currentChildHash, + right: t.zeroHashes[h], } + // Update cache + // TODO: review this part of the logic, skipping ?optimizaton? + // from OG implementation + t.lastLeftCache[h] = currentChildHash } currentChildHash = parent.hash() newNodes = append(newNodes, parent) @@ -73,10 +162,155 @@ func (t *tree) addLeaf(index uint, hash common.Hash) error { // store root root := currentChildHash + if err := tx.Put(t.rootTable, dbCommon.Uint32ToBytes(depositCount), root[:]); err != nil { + return err + } + // store nodes + for _, node := range newNodes { + value, err := node.MarshalBinary() + if err != nil { + return err + } + if err := tx.Put(t.rhtTable, node.hash().Bytes(), value); err != nil { + return err + } + } - t.lastIndex++ + t.lastDepositCount++ return nil } -// TODO: handle rerog: lastIndex & rightPathCache +func (t *tree) initLastLeftCacheAndLastDepositCount(ctx context.Context) error { + tx, err := t.db.BeginRw(ctx) + if err != nil { + return err + } + defer tx.Rollback() + + root, err := t.initLastDepositCount(tx) + if err != nil { + return err + } + return t.initLastLeftCache(tx, t.lastDepositCount, root) +} + +// getLastDepositCountAndRoot return the deposit count and the root associated to the last deposit. +// If deposit count == -1, it means no deposit added yet +func (t *tree) getLastDepositCountAndRoot(tx kv.Tx) (int64, common.Hash, error) { + iter, err := tx.RangeDescend( + t.rootTable, + dbCommon.Uint32ToBytes(math.MaxUint32), + dbCommon.Uint32ToBytes(0), + 1, + ) + if err != nil { + return 0, common.Hash{}, err + } + + lastDepositCountBytes, rootBytes, err := iter.Next() + if err != nil { + return 0, common.Hash{}, err + } + if lastDepositCountBytes == nil { + return -1, common.Hash{}, nil + } + return int64(dbCommon.BytesToUint32(lastDepositCountBytes)), common.Hash(rootBytes), nil +} + +func (t *tree) initLastDepositCount(tx kv.Tx) (common.Hash, error) { + ldc, root, err := t.getLastDepositCountAndRoot(tx) + if err != nil { + return common.Hash{}, err + } + t.lastDepositCount = ldc + return root, nil +} + +func (t *tree) initLastLeftCache(tx kv.Tx, lastDepositCount int64, lastRoot common.Hash) error { + siblings := make([]common.Hash, t.height, t.height) + if lastDepositCount == -1 { + t.lastLeftCache = siblings + return nil + } + index := t.lastDepositCount + + currentNodeHash := lastRoot + // It starts in height-1 because 0 is the level of the leafs + for h := int(t.height - 1); h >= 0; h-- { + currentNode, err := t.getRHTNode(tx, currentNodeHash) + if err != nil { + return fmt.Errorf( + "error getting node %s from the RHT at height %d with root %s: %v", + currentNodeHash.Hex(), h, lastRoot.Hex(), err, + ) + } + siblings = append(siblings, currentNode.left) + if index&(1< 0 { + currentNodeHash = currentNode.right + } else { + currentNodeHash = currentNode.left + } + } + + // Reverse the siblings to go from leafs to root + for i, j := 0, len(siblings)-1; i < j; i, j = i+1, j-1 { + siblings[i], siblings[j] = siblings[j], siblings[i] + } + + t.lastLeftCache = siblings + return nil +} + +func (t *tree) getRHTNode(tx kv.Tx, nodeHash common.Hash) (*treeNode, error) { + nodeBytes, err := tx.GetOne(t.rhtTable, nodeHash[:]) + if err != nil { + return nil, err + } + if nodeBytes == nil { + return nil, ErrNotFound + } + node := &treeNode{} + err = node.UnmarshalBinary(nodeBytes) + return node, err +} + +func (t *tree) reorg(tx kv.RwTx, lastValidDepositCount uint32) error { + // Clean root table + for i := lastValidDepositCount + 1; i <= uint32(t.lastDepositCount); i++ { + if err := tx.Delete(t.rootTable, dbCommon.Uint32ToBytes(i)); err != nil { + return err + } + } + + // Reset cache + rootBytes, err := tx.GetOne(t.rootTable, dbCommon.Uint32ToBytes(lastValidDepositCount)) + if err != nil { + return err + } + err = t.initLastLeftCache(tx, int64(lastValidDepositCount), common.Hash(rootBytes)) + if err != nil { + return err + } + + // Note: not cleaning RHT, not worth it + t.lastDepositCount = int64(lastValidDepositCount) + return nil +} + +func generateZeroHashes(height uint8) []common.Hash { + var zeroHashes = []common.Hash{ + {}, + } + // This generates a leaf = HashZero in position 0. In the rest of the positions that are equivalent to the ascending levels, + // we set the hashes of the nodes. So all nodes from level i=5 will have the same value and same children nodes. + for i := 1; i <= int(height); i++ { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(zeroHashes[i-1][:]) + hasher.Write(zeroHashes[i-1][:]) + thisHeightHash := common.Hash{} + copy(thisHeightHash[:], hasher.Sum(nil)) + zeroHashes = append(zeroHashes, thisHeightHash) + } + return zeroHashes +} diff --git a/bridgesync/tree_test.go b/bridgesync/tree_test.go new file mode 100644 index 00000000..5624ad47 --- /dev/null +++ b/bridgesync/tree_test.go @@ -0,0 +1,141 @@ +package bridgesync + +import ( + "context" + "encoding/json" + "fmt" + "math/big" + "os" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +// MTRootVectorRaw represents the root of Merkle Tree +type MTRootVectorRaw struct { + ExistingLeaves []string `json:"previousLeafsValues"` + CurrentRoot string `json:"currentRoot"` + NewLeaf DepositVectorRaw `json:"newLeaf"` + NewRoot string `json:"newRoot"` +} + +func TestMTAddLeaf(t *testing.T) { + data, err := os.ReadFile("testvectors/root-vectors.json") + require.NoError(t, err) + + var mtTestVectors []MTRootVectorRaw + err = json.Unmarshal(data, &mtTestVectors) + require.NoError(t, err) + ctx := context.Background() + + for ti, testVector := range mtTestVectors { + t.Run(fmt.Sprintf("Test vector %d", ti), func(t *testing.T) { + + path := t.TempDir() + p, err := newProcessor(context.Background(), path, "foo") + require.NoError(t, err) + + // Add exisiting leaves + for i, leaf := range testVector.ExistingLeaves { + tx, err := p.db.BeginRw(ctx) + require.NoError(t, err) + err = p.tree.addLeaf(tx, uint32(i), common.HexToHash(leaf)) + require.NoError(t, err) + err = tx.Commit() + require.NoError(t, err) + } + if len(testVector.ExistingLeaves) > 0 { + txRo, err := p.db.BeginRo(ctx) + require.NoError(t, err) + _, actualRoot, err := p.tree.getLastDepositCountAndRoot(txRo) + txRo.Rollback() + require.NoError(t, err) + require.Equal(t, common.HexToHash(testVector.CurrentRoot), actualRoot) + } + + // Add new bridge + amount, result := big.NewInt(0).SetString(testVector.NewLeaf.Amount, 0) + require.True(t, result) + bridge := Bridge{ + OriginNetwork: testVector.NewLeaf.OriginalNetwork, + OriginAddress: common.HexToAddress(testVector.NewLeaf.TokenAddress), + Amount: amount, + DestinationNetwork: testVector.NewLeaf.DestinationNetwork, + DestinationAddress: common.HexToAddress(testVector.NewLeaf.DestinationAddress), + DepositCount: uint32(len(testVector.ExistingLeaves)), + Metadata: common.FromHex(testVector.NewLeaf.Metadata), + } + tx, err := p.db.BeginRw(ctx) + require.NoError(t, err) + require.Equal(t, common.HexToHash(testVector.NewLeaf.CurrentHash), bridge.Hash()) + err = p.tree.addLeaf(tx, bridge.DepositCount, bridge.Hash()) + require.NoError(t, err) + err = tx.Commit() + txRo, err := p.db.BeginRo(ctx) + require.NoError(t, err) + _, actualRoot, err := p.tree.getLastDepositCountAndRoot(txRo) + txRo.Rollback() + require.NoError(t, err) + require.Equal(t, common.HexToHash(testVector.NewRoot), actualRoot) + }) + } +} + +// MTClaimVectorRaw represents the merkle proof +type MTClaimVectorRaw struct { + Deposits []DepositVectorRaw `json:"leafs"` + Index uint32 `json:"index"` + MerkleProof []string `json:"proof"` + ExpectedRoot string `json:"root"` +} + +func TestMTGetProof(t *testing.T) { + data, err := os.ReadFile("testvectors/claim-vectors.json") + require.NoError(t, err) + + var mtTestVectors []MTClaimVectorRaw + err = json.Unmarshal(data, &mtTestVectors) + require.NoError(t, err) + ctx := context.Background() + + for ti, testVector := range mtTestVectors { + t.Run(fmt.Sprintf("Test vector %d", ti), func(t *testing.T) { + path := t.TempDir() + p, err := newProcessor(context.Background(), path, "foo") + require.NoError(t, err) + + for li, leaf := range testVector.Deposits { + amount, result := big.NewInt(0).SetString(leaf.Amount, 0) + require.True(t, result) + bridge := &Bridge{ + OriginNetwork: leaf.OriginalNetwork, + OriginAddress: common.HexToAddress(leaf.TokenAddress), + Amount: amount, + DestinationNetwork: leaf.DestinationNetwork, + DestinationAddress: common.HexToAddress(leaf.DestinationAddress), + DepositCount: uint32(li), + Metadata: common.FromHex(leaf.Metadata), + } + tx, err := p.db.BeginRw(ctx) + require.NoError(t, err) + err = p.tree.addLeaf(tx, bridge.DepositCount, bridge.Hash()) + require.NoError(t, err) + err = tx.Commit() + require.NoError(t, err) + } + txRo, err := p.db.BeginRo(ctx) + require.NoError(t, err) + _, actualRoot, err := p.tree.getLastDepositCountAndRoot(txRo) + txRo.Rollback() + expectedRoot := common.HexToHash(testVector.ExpectedRoot) + require.Equal(t, expectedRoot, actualRoot) + + proof, err := p.tree.getProof(ctx, testVector.Index, expectedRoot) + require.NoError(t, err) + for i, sibling := range testVector.MerkleProof { + require.Equal(t, common.HexToHash(sibling), proof[i]) + } + }) + } +}