From 9c8835bd790732c61d0da189f65ea7f2fc50f808 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 24 Jul 2024 16:42:09 +0300 Subject: [PATCH 001/199] remove subnet IDs from uptime pkg --- snow/uptime/locked_calculator.go | 12 +- snow/uptime/locked_calculator_test.go | 19 +- snow/uptime/manager.go | 109 ++++------ snow/uptime/manager_test.go | 291 +++++++++++--------------- snow/uptime/no_op_calculator.go | 6 +- snow/uptime/state.go | 21 +- snow/uptime/test_state.go | 25 +-- 7 files changed, 200 insertions(+), 283 deletions(-) diff --git a/snow/uptime/locked_calculator.go b/snow/uptime/locked_calculator.go index 884878ab24f6..a1e9537a5e2f 100644 --- a/snow/uptime/locked_calculator.go +++ b/snow/uptime/locked_calculator.go @@ -35,7 +35,7 @@ func NewLockedCalculator() LockedCalculator { return &lockedCalculator{} } -func (c *lockedCalculator) CalculateUptime(nodeID ids.NodeID, subnetID ids.ID) (time.Duration, time.Time, error) { +func (c *lockedCalculator) CalculateUptime(nodeID ids.NodeID) (time.Duration, time.Time, error) { c.lock.RLock() defer c.lock.RUnlock() @@ -46,10 +46,10 @@ func (c *lockedCalculator) CalculateUptime(nodeID ids.NodeID, subnetID ids.ID) ( c.calculatorLock.Lock() defer c.calculatorLock.Unlock() - return c.c.CalculateUptime(nodeID, subnetID) + return c.c.CalculateUptime(nodeID) } -func (c *lockedCalculator) CalculateUptimePercent(nodeID ids.NodeID, subnetID ids.ID) (float64, error) { +func (c *lockedCalculator) CalculateUptimePercent(nodeID ids.NodeID) (float64, error) { c.lock.RLock() defer c.lock.RUnlock() @@ -60,10 +60,10 @@ func (c *lockedCalculator) CalculateUptimePercent(nodeID ids.NodeID, subnetID id c.calculatorLock.Lock() defer c.calculatorLock.Unlock() - return c.c.CalculateUptimePercent(nodeID, subnetID) + return c.c.CalculateUptimePercent(nodeID) } -func (c *lockedCalculator) CalculateUptimePercentFrom(nodeID ids.NodeID, subnetID ids.ID, startTime time.Time) (float64, error) { +func (c *lockedCalculator) CalculateUptimePercentFrom(nodeID ids.NodeID, startTime time.Time) (float64, error) { c.lock.RLock() defer c.lock.RUnlock() @@ -74,7 +74,7 @@ func (c *lockedCalculator) CalculateUptimePercentFrom(nodeID ids.NodeID, subnetI c.calculatorLock.Lock() defer c.calculatorLock.Unlock() - return c.c.CalculateUptimePercentFrom(nodeID, subnetID, startTime) + return c.c.CalculateUptimePercentFrom(nodeID, startTime) } func (c *lockedCalculator) SetCalculator(isBootstrapped *utils.Atomic[bool], lock sync.Locker, newC Calculator) { diff --git a/snow/uptime/locked_calculator_test.go b/snow/uptime/locked_calculator_test.go index 966722f6457d..9e5edaad8c63 100644 --- a/snow/uptime/locked_calculator_test.go +++ b/snow/uptime/locked_calculator_test.go @@ -24,14 +24,13 @@ func TestLockedCalculator(t *testing.T) { // Should still error because ctx is nil nodeID := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() - _, _, err := lc.CalculateUptime(nodeID, subnetID) + _, _, err := lc.CalculateUptime(nodeID) require.ErrorIs(err, errStillBootstrapping) - _, err = lc.CalculateUptimePercent(nodeID, subnetID) + _, err = lc.CalculateUptimePercent(nodeID) require.ErrorIs(err, errStillBootstrapping) - _, err = lc.CalculateUptimePercentFrom(nodeID, subnetID, time.Now()) + _, err = lc.CalculateUptimePercentFrom(nodeID, time.Now()) require.ErrorIs(err, errStillBootstrapping) var isBootstrapped utils.Atomic[bool] @@ -39,27 +38,27 @@ func TestLockedCalculator(t *testing.T) { // Should still error because ctx is not bootstrapped lc.SetCalculator(&isBootstrapped, &sync.Mutex{}, mockCalc) - _, _, err = lc.CalculateUptime(nodeID, subnetID) + _, _, err = lc.CalculateUptime(nodeID) require.ErrorIs(err, errStillBootstrapping) - _, err = lc.CalculateUptimePercent(nodeID, subnetID) + _, err = lc.CalculateUptimePercent(nodeID) require.ErrorIs(err, errStillBootstrapping) - _, err = lc.CalculateUptimePercentFrom(nodeID, subnetID, time.Now()) + _, err = lc.CalculateUptimePercentFrom(nodeID, time.Now()) require.ErrorIs(err, errStillBootstrapping) isBootstrapped.Set(true) // Should return the value from the mocked inner calculator mockCalc.EXPECT().CalculateUptime(gomock.Any(), gomock.Any()).AnyTimes().Return(time.Duration(0), time.Time{}, errTest) - _, _, err = lc.CalculateUptime(nodeID, subnetID) + _, _, err = lc.CalculateUptime(nodeID) require.ErrorIs(err, errTest) mockCalc.EXPECT().CalculateUptimePercent(gomock.Any(), gomock.Any()).AnyTimes().Return(float64(0), errTest) - _, err = lc.CalculateUptimePercent(nodeID, subnetID) + _, err = lc.CalculateUptimePercent(nodeID) require.ErrorIs(err, errTest) mockCalc.EXPECT().CalculateUptimePercentFrom(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(float64(0), errTest) - _, err = lc.CalculateUptimePercentFrom(nodeID, subnetID, time.Now()) + _, err = lc.CalculateUptimePercentFrom(nodeID, time.Now()) require.ErrorIs(err, errTest) } diff --git a/snow/uptime/manager.go b/snow/uptime/manager.go index a64b71ca62de..2a915da3fb6c 100644 --- a/snow/uptime/manager.go +++ b/snow/uptime/manager.go @@ -8,7 +8,6 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/timer/mockable" ) @@ -20,42 +19,41 @@ type Manager interface { } type Tracker interface { - StartTracking(nodeIDs []ids.NodeID, subnetID ids.ID) error - StopTracking(nodeIDs []ids.NodeID, subnetID ids.ID) error + StartTracking(nodeIDs []ids.NodeID) error + StopTracking(nodeIDs []ids.NodeID) error - Connect(nodeID ids.NodeID, subnetID ids.ID) error - IsConnected(nodeID ids.NodeID, subnetID ids.ID) bool + Connect(nodeID ids.NodeID) error + IsConnected(nodeID ids.NodeID) bool Disconnect(nodeID ids.NodeID) error } type Calculator interface { - CalculateUptime(nodeID ids.NodeID, subnetID ids.ID) (time.Duration, time.Time, error) - CalculateUptimePercent(nodeID ids.NodeID, subnetID ids.ID) (float64, error) + CalculateUptime(nodeID ids.NodeID) (time.Duration, time.Time, error) + CalculateUptimePercent(nodeID ids.NodeID) (float64, error) // CalculateUptimePercentFrom expects [startTime] to be truncated (floored) to the nearest second - CalculateUptimePercentFrom(nodeID ids.NodeID, subnetID ids.ID, startTime time.Time) (float64, error) + CalculateUptimePercentFrom(nodeID ids.NodeID, startTime time.Time) (float64, error) } type manager struct { // Used to get time. Useful for faking time during tests. clock *mockable.Clock - state State - connections map[ids.NodeID]map[ids.ID]time.Time // nodeID -> subnetID -> time - trackedSubnets set.Set[ids.ID] + state State + connections map[ids.NodeID]time.Time // nodeID -> time } func NewManager(state State, clk *mockable.Clock) Manager { return &manager{ clock: clk, state: state, - connections: make(map[ids.NodeID]map[ids.ID]time.Time), + connections: make(map[ids.NodeID]time.Time), } } -func (m *manager) StartTracking(nodeIDs []ids.NodeID, subnetID ids.ID) error { +func (m *manager) StartTracking(nodeIDs []ids.NodeID) error { now := m.clock.UnixTime() for _, nodeID := range nodeIDs { - upDuration, lastUpdated, err := m.state.GetUptime(nodeID, subnetID) + upDuration, lastUpdated, err := m.state.GetUptime(nodeID) if err != nil { return err } @@ -68,33 +66,30 @@ func (m *manager) StartTracking(nodeIDs []ids.NodeID, subnetID ids.ID) error { durationOffline := now.Sub(lastUpdated) newUpDuration := upDuration + durationOffline - if err := m.state.SetUptime(nodeID, subnetID, newUpDuration, now); err != nil { + if err := m.state.SetUptime(nodeID, newUpDuration, now); err != nil { return err } } - m.trackedSubnets.Add(subnetID) return nil } -func (m *manager) StopTracking(nodeIDs []ids.NodeID, subnetID ids.ID) error { +func (m *manager) StopTracking(nodeIDs []ids.NodeID) error { now := m.clock.UnixTime() for _, nodeID := range nodeIDs { - connectedSubnets := m.connections[nodeID] - // If the node is already connected to this subnet, then we can just + // If the node is already connected, then we can just // update the uptime in the state and remove the connection - if _, isConnected := connectedSubnets[subnetID]; isConnected { - if err := m.updateSubnetUptime(nodeID, subnetID); err != nil { - delete(connectedSubnets, subnetID) + if _, isConnected := m.connections[nodeID]; isConnected { + err := m.updateUptime(nodeID) + delete(m.connections, nodeID) + if err != nil { return err } - delete(connectedSubnets, subnetID) continue } - // if the node is not connected to this subnet, then we need to update - // the uptime in the state from the last time the node was connected to - // this subnet to now. - upDuration, lastUpdated, err := m.state.GetUptime(nodeID, subnetID) + // if the node is not connected, then we need to update + // the uptime in the state from the last time the node was connected to current time. + upDuration, lastUpdated, err := m.state.GetUptime(nodeID) if err != nil { return err } @@ -105,41 +100,34 @@ func (m *manager) StopTracking(nodeIDs []ids.NodeID, subnetID ids.ID) error { continue } - if err := m.state.SetUptime(nodeID, subnetID, upDuration, now); err != nil { + if err := m.state.SetUptime(nodeID, upDuration, now); err != nil { return err } } return nil } -func (m *manager) Connect(nodeID ids.NodeID, subnetID ids.ID) error { - subnetConnections, ok := m.connections[nodeID] - if !ok { - subnetConnections = make(map[ids.ID]time.Time) - m.connections[nodeID] = subnetConnections - } - subnetConnections[subnetID] = m.clock.UnixTime() +func (m *manager) Connect(nodeID ids.NodeID) error { + m.connections[nodeID] = m.clock.UnixTime() return nil } -func (m *manager) IsConnected(nodeID ids.NodeID, subnetID ids.ID) bool { - _, connected := m.connections[nodeID][subnetID] +func (m *manager) IsConnected(nodeID ids.NodeID) bool { + _, connected := m.connections[nodeID] return connected } func (m *manager) Disconnect(nodeID ids.NodeID) error { - // Update every subnet that this node was connected to - for subnetID := range m.connections[nodeID] { - if err := m.updateSubnetUptime(nodeID, subnetID); err != nil { - return err - } + if err := m.updateUptime(nodeID); err != nil { + return err } + // TODO: shall we delete the connection regardless of the error? delete(m.connections, nodeID) return nil } -func (m *manager) CalculateUptime(nodeID ids.NodeID, subnetID ids.ID) (time.Duration, time.Time, error) { - upDuration, lastUpdated, err := m.state.GetUptime(nodeID, subnetID) +func (m *manager) CalculateUptime(nodeID ids.NodeID) (time.Duration, time.Time, error) { + upDuration, lastUpdated, err := m.state.GetUptime(nodeID) if err != nil { return 0, time.Time{}, err } @@ -151,13 +139,7 @@ func (m *manager) CalculateUptime(nodeID ids.NodeID, subnetID ids.ID) (time.Dura return upDuration, lastUpdated, nil } - if !m.trackedSubnets.Contains(subnetID) { - durationOffline := now.Sub(lastUpdated) - newUpDuration := upDuration + durationOffline - return newUpDuration, now, nil - } - - timeConnected, isConnected := m.connections[nodeID][subnetID] + timeConnected, isConnected := m.connections[nodeID] if !isConnected { return upDuration, now, nil } @@ -181,16 +163,16 @@ func (m *manager) CalculateUptime(nodeID ids.NodeID, subnetID ids.ID) (time.Dura return newUpDuration, now, nil } -func (m *manager) CalculateUptimePercent(nodeID ids.NodeID, subnetID ids.ID) (float64, error) { - startTime, err := m.state.GetStartTime(nodeID, subnetID) +func (m *manager) CalculateUptimePercent(nodeID ids.NodeID) (float64, error) { + startTime, err := m.state.GetStartTime(nodeID) if err != nil { return 0, err } - return m.CalculateUptimePercentFrom(nodeID, subnetID, startTime) + return m.CalculateUptimePercentFrom(nodeID, startTime) } -func (m *manager) CalculateUptimePercentFrom(nodeID ids.NodeID, subnetID ids.ID, startTime time.Time) (float64, error) { - upDuration, now, err := m.CalculateUptime(nodeID, subnetID) +func (m *manager) CalculateUptimePercentFrom(nodeID ids.NodeID, startTime time.Time) (float64, error) { + upDuration, now, err := m.CalculateUptime(nodeID) if err != nil { return 0, err } @@ -202,15 +184,10 @@ func (m *manager) CalculateUptimePercentFrom(nodeID ids.NodeID, subnetID ids.ID, return uptime, nil } -// updateSubnetUptime updates the subnet uptime of the node on the state by the amount -// of time that the node has been connected to the subnet. -func (m *manager) updateSubnetUptime(nodeID ids.NodeID, subnetID ids.ID) error { - // we're not tracking this subnet, skip updating it. - if !m.trackedSubnets.Contains(subnetID) { - return nil - } - - newDuration, newLastUpdated, err := m.CalculateUptime(nodeID, subnetID) +// updateUptime updates the uptime of the node on the state by the amount +// of time that the node has been connected. +func (m *manager) updateUptime(nodeID ids.NodeID) error { + newDuration, newLastUpdated, err := m.CalculateUptime(nodeID) if err == database.ErrNotFound { // If a non-validator disconnects, we don't care return nil @@ -219,5 +196,5 @@ func (m *manager) updateSubnetUptime(nodeID ids.NodeID, subnetID ids.ID) error { return err } - return m.state.SetUptime(nodeID, subnetID, newDuration, newLastUpdated) + return m.state.SetUptime(nodeID, newDuration, newLastUpdated) } diff --git a/snow/uptime/manager_test.go b/snow/uptime/manager_test.go index e04fcc3a9fbe..ef3de35070b9 100644 --- a/snow/uptime/manager_test.go +++ b/snow/uptime/manager_test.go @@ -21,11 +21,10 @@ func TestStartTracking(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() startTime := time.Now() s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) @@ -33,9 +32,9 @@ func TestStartTracking(t *testing.T) { currentTime := startTime.Add(time.Second) clk.Set(currentTime) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) - duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID) + duration, lastUpdated, err := up.CalculateUptime(nodeID0) require.NoError(err) require.Equal(time.Second, duration) require.Equal(clk.UnixTime(), lastUpdated) @@ -45,12 +44,11 @@ func TestStartTrackingDBError(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() startTime := time.Now() s := NewTestState() s.dbWriteError = errTest - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) @@ -58,7 +56,7 @@ func TestStartTrackingDBError(t *testing.T) { currentTime := startTime.Add(time.Second) clk.Set(currentTime) - err := up.StartTracking([]ids.NodeID{nodeID0}, subnetID) + err := up.StartTracking([]ids.NodeID{nodeID0}) require.ErrorIs(err, errTest) } @@ -70,9 +68,8 @@ func TestStartTrackingNonValidator(t *testing.T) { up := NewManager(s, &clk) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() - err := up.StartTracking([]ids.NodeID{nodeID0}, subnetID) + err := up.StartTracking([]ids.NodeID{nodeID0}) require.ErrorIs(err, database.ErrNotFound) } @@ -80,11 +77,10 @@ func TestStartTrackingInThePast(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() startTime := time.Now() s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) @@ -92,9 +88,9 @@ func TestStartTrackingInThePast(t *testing.T) { currentTime := startTime.Add(-time.Second) clk.Set(currentTime) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) - duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID) + duration, lastUpdated, err := up.CalculateUptime(nodeID0) require.NoError(err) require.Equal(time.Duration(0), duration) require.Equal(startTime.Truncate(time.Second), lastUpdated) @@ -104,29 +100,28 @@ func TestStopTrackingDecreasesUptime(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() currentTime := time.Now() startTime := currentTime s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) clk.Set(currentTime) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) currentTime = startTime.Add(time.Second) clk.Set(currentTime) - require.NoError(up.StopTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StopTracking([]ids.NodeID{nodeID0})) up = NewManager(s, &clk) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) - duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID) + duration, lastUpdated, err := up.CalculateUptime(nodeID0) require.NoError(err) require.Equal(time.Duration(0), duration) require.Equal(clk.UnixTime(), lastUpdated) @@ -136,31 +131,30 @@ func TestStopTrackingIncreasesUptime(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() currentTime := time.Now() startTime := currentTime s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) clk.Set(currentTime) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) - require.NoError(up.Connect(nodeID0, subnetID)) + require.NoError(up.Connect(nodeID0)) currentTime = startTime.Add(time.Second) clk.Set(currentTime) - require.NoError(up.StopTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StopTracking([]ids.NodeID{nodeID0})) up = NewManager(s, &clk) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) - duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID) + duration, lastUpdated, err := up.CalculateUptime(nodeID0) require.NoError(err) require.Equal(time.Second, duration) require.Equal(clk.UnixTime(), lastUpdated) @@ -170,15 +164,14 @@ func TestStopTrackingDisconnectedNonValidator(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() s := NewTestState() clk := mockable.Clock{} up := NewManager(s, &clk) - require.NoError(up.StartTracking(nil, subnetID)) + require.NoError(up.StartTracking(nil)) - err := up.StopTracking([]ids.NodeID{nodeID0}, subnetID) + err := up.StopTracking([]ids.NodeID{nodeID0}) require.ErrorIs(err, database.ErrNotFound) } @@ -186,20 +179,19 @@ func TestStopTrackingConnectedDBError(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() startTime := time.Now() s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) - require.NoError(up.StartTracking(nil, subnetID)) + require.NoError(up.StartTracking(nil)) - require.NoError(up.Connect(nodeID0, subnetID)) + require.NoError(up.Connect(nodeID0)) s.dbReadError = errTest - err := up.StopTracking([]ids.NodeID{nodeID0}, subnetID) + err := up.StopTracking([]ids.NodeID{nodeID0}) require.ErrorIs(err, errTest) } @@ -207,24 +199,23 @@ func TestStopTrackingNonConnectedPast(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() currentTime := time.Now() startTime := currentTime s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) clk.Set(currentTime) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) currentTime = currentTime.Add(-time.Second) clk.Set(currentTime) - require.NoError(up.StopTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StopTracking([]ids.NodeID{nodeID0})) - duration, lastUpdated, err := s.GetUptime(nodeID0, subnetID) + duration, lastUpdated, err := s.GetUptime(nodeID0) require.NoError(err) require.Equal(time.Duration(0), duration) require.Equal(startTime.Truncate(time.Second), lastUpdated) @@ -234,131 +225,104 @@ func TestStopTrackingNonConnectedDBError(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() currentTime := time.Now() startTime := currentTime s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) clk.Set(currentTime) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) currentTime = currentTime.Add(time.Second) clk.Set(currentTime) s.dbWriteError = errTest - err := up.StopTracking([]ids.NodeID{nodeID0}, subnetID) + err := up.StopTracking([]ids.NodeID{nodeID0}) require.ErrorIs(err, errTest) } func TestConnectAndDisconnect(t *testing.T) { - tests := []struct { - name string - subnetIDs []ids.ID - }{ - { - name: "Single Subnet", - subnetIDs: []ids.ID{ids.GenerateTestID()}, - }, - { - name: "Multiple Subnets", - subnetIDs: []ids.ID{ids.GenerateTestID(), ids.GenerateTestID()}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - - nodeID0 := ids.GenerateTestNodeID() - currentTime := time.Now() - startTime := currentTime - - s := NewTestState() - clk := mockable.Clock{} - up := NewManager(s, &clk) - clk.Set(currentTime) - - for _, subnetID := range tt.subnetIDs { - s.AddNode(nodeID0, subnetID, startTime) - - connected := up.IsConnected(nodeID0, subnetID) - require.False(connected) - - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) - - connected = up.IsConnected(nodeID0, subnetID) - require.False(connected) - - duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID) - require.NoError(err) - require.Equal(time.Duration(0), duration) - require.Equal(clk.UnixTime(), lastUpdated) - - require.NoError(up.Connect(nodeID0, subnetID)) - - connected = up.IsConnected(nodeID0, subnetID) - require.True(connected) - } - - currentTime = currentTime.Add(time.Second) - clk.Set(currentTime) - - for _, subnetID := range tt.subnetIDs { - duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID) - require.NoError(err) - require.Equal(time.Second, duration) - require.Equal(clk.UnixTime(), lastUpdated) - } - - require.NoError(up.Disconnect(nodeID0)) - - for _, subnetID := range tt.subnetIDs { - connected := up.IsConnected(nodeID0, subnetID) - require.False(connected) - } - - currentTime = currentTime.Add(time.Second) - clk.Set(currentTime) - - for _, subnetID := range tt.subnetIDs { - duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID) - require.NoError(err) - require.Equal(time.Second, duration) - require.Equal(clk.UnixTime(), lastUpdated) - } - }) - } + require := require.New(t) + + nodeID0 := ids.GenerateTestNodeID() + currentTime := time.Now() + startTime := currentTime + + s := NewTestState() + clk := mockable.Clock{} + up := NewManager(s, &clk) + clk.Set(currentTime) + + s.AddNode(nodeID0, startTime) + + connected := up.IsConnected(nodeID0) + require.False(connected) + + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) + + connected = up.IsConnected(nodeID0) + require.False(connected) + + duration, lastUpdated, err := up.CalculateUptime(nodeID0) + require.NoError(err) + require.Equal(time.Duration(0), duration) + require.Equal(clk.UnixTime(), lastUpdated) + + require.NoError(up.Connect(nodeID0)) + + connected = up.IsConnected(nodeID0) + require.True(connected) + + currentTime = currentTime.Add(time.Second) + clk.Set(currentTime) + + duration, lastUpdated, err = up.CalculateUptime(nodeID0) + require.NoError(err) + require.Equal(time.Second, duration) + require.Equal(clk.UnixTime(), lastUpdated) + + require.NoError(up.Disconnect(nodeID0)) + + connected = up.IsConnected(nodeID0) + require.False(connected) + + currentTime = currentTime.Add(time.Second) + clk.Set(currentTime) + + duration, lastUpdated, err = up.CalculateUptime(nodeID0) + require.NoError(err) + require.Equal(time.Second, duration) + require.Equal(clk.UnixTime(), lastUpdated) } func TestConnectAndDisconnectBeforeTracking(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() currentTime := time.Now() startTime := currentTime s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) currentTime = currentTime.Add(time.Second) clk.Set(currentTime) - require.NoError(up.Connect(nodeID0, subnetID)) + require.NoError(up.Connect(nodeID0)) currentTime = currentTime.Add(time.Second) clk.Set(currentTime) require.NoError(up.Disconnect(nodeID0)) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) - duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID) + duration, lastUpdated, err := up.CalculateUptime(nodeID0) require.NoError(err) require.Equal(2*time.Second, duration) require.Equal(clk.UnixTime(), lastUpdated) @@ -368,33 +332,32 @@ func TestUnrelatedNodeDisconnect(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() nodeID1 := ids.GenerateTestNodeID() currentTime := time.Now() startTime := currentTime s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) clk.Set(currentTime) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) - duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID) + duration, lastUpdated, err := up.CalculateUptime(nodeID0) require.NoError(err) require.Equal(time.Duration(0), duration) require.Equal(clk.UnixTime(), lastUpdated) - require.NoError(up.Connect(nodeID0, subnetID)) + require.NoError(up.Connect(nodeID0)) - require.NoError(up.Connect(nodeID1, subnetID)) + require.NoError(up.Connect(nodeID1)) currentTime = currentTime.Add(time.Second) clk.Set(currentTime) - duration, lastUpdated, err = up.CalculateUptime(nodeID0, subnetID) + duration, lastUpdated, err = up.CalculateUptime(nodeID0) require.NoError(err) require.Equal(time.Second, duration) require.Equal(clk.UnixTime(), lastUpdated) @@ -404,7 +367,7 @@ func TestUnrelatedNodeDisconnect(t *testing.T) { currentTime = currentTime.Add(time.Second) clk.Set(currentTime) - duration, lastUpdated, err = up.CalculateUptime(nodeID0, subnetID) + duration, lastUpdated, err = up.CalculateUptime(nodeID0) require.NoError(err) require.Equal(2*time.Second, duration) require.Equal(clk.UnixTime(), lastUpdated) @@ -414,11 +377,10 @@ func TestCalculateUptimeWhenNeverTracked(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() startTime := time.Now() s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) @@ -426,12 +388,12 @@ func TestCalculateUptimeWhenNeverTracked(t *testing.T) { currentTime := startTime.Add(time.Second) clk.Set(currentTime) - duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID) + duration, lastUpdated, err := up.CalculateUptime(nodeID0) require.NoError(err) require.Equal(time.Second, duration) require.Equal(clk.UnixTime(), lastUpdated) - uptime, err := up.CalculateUptimePercentFrom(nodeID0, subnetID, startTime.Truncate(time.Second)) + uptime, err := up.CalculateUptimePercentFrom(nodeID0, startTime.Truncate(time.Second)) require.NoError(err) require.Equal(float64(1), uptime) } @@ -440,7 +402,6 @@ func TestCalculateUptimeWhenNeverConnected(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() startTime := time.Now() s := NewTestState() @@ -448,19 +409,19 @@ func TestCalculateUptimeWhenNeverConnected(t *testing.T) { clk := mockable.Clock{} up := NewManager(s, &clk) - require.NoError(up.StartTracking([]ids.NodeID{}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{})) - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) currentTime := startTime.Add(time.Second) clk.Set(currentTime) - duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID) + duration, lastUpdated, err := up.CalculateUptime(nodeID0) require.NoError(err) require.Equal(time.Duration(0), duration) require.Equal(clk.UnixTime(), lastUpdated) - uptime, err := up.CalculateUptimePercentFrom(nodeID0, subnetID, startTime) + uptime, err := up.CalculateUptimePercentFrom(nodeID0, startTime) require.NoError(err) require.Equal(float64(0), uptime) } @@ -469,28 +430,27 @@ func TestCalculateUptimeWhenConnectedBeforeTracking(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() currentTime := time.Now() startTime := currentTime s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) clk.Set(currentTime) - require.NoError(up.Connect(nodeID0, subnetID)) + require.NoError(up.Connect(nodeID0)) currentTime = currentTime.Add(time.Second) clk.Set(currentTime) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) currentTime = currentTime.Add(time.Second) clk.Set(currentTime) - duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID) + duration, lastUpdated, err := up.CalculateUptime(nodeID0) require.NoError(err) require.Equal(2*time.Second, duration) require.Equal(clk.UnixTime(), lastUpdated) @@ -500,28 +460,27 @@ func TestCalculateUptimeWhenConnectedInFuture(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() currentTime := time.Now() startTime := currentTime s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) clk.Set(currentTime) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) currentTime = currentTime.Add(2 * time.Second) clk.Set(currentTime) - require.NoError(up.Connect(nodeID0, subnetID)) + require.NoError(up.Connect(nodeID0)) currentTime = currentTime.Add(-time.Second) clk.Set(currentTime) - duration, lastUpdated, err := up.CalculateUptime(nodeID0, subnetID) + duration, lastUpdated, err := up.CalculateUptime(nodeID0) require.NoError(err) require.Equal(time.Duration(0), duration) require.Equal(clk.UnixTime(), lastUpdated) @@ -531,7 +490,6 @@ func TestCalculateUptimeNonValidator(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() startTime := time.Now() s := NewTestState() @@ -539,7 +497,7 @@ func TestCalculateUptimeNonValidator(t *testing.T) { clk := mockable.Clock{} up := NewManager(s, &clk) - _, err := up.CalculateUptimePercentFrom(nodeID0, subnetID, startTime) + _, err := up.CalculateUptimePercentFrom(nodeID0, startTime) require.ErrorIs(err, database.ErrNotFound) } @@ -547,18 +505,17 @@ func TestCalculateUptimePercentageDivBy0(t *testing.T) { require := require.New(t) nodeID0 := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() currentTime := time.Now() startTime := currentTime s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) clk.Set(currentTime) - uptime, err := up.CalculateUptimePercentFrom(nodeID0, subnetID, startTime.Truncate(time.Second)) + uptime, err := up.CalculateUptimePercentFrom(nodeID0, startTime.Truncate(time.Second)) require.NoError(err) require.Equal(float64(1), uptime) } @@ -568,21 +525,20 @@ func TestCalculateUptimePercentage(t *testing.T) { nodeID0 := ids.GenerateTestNodeID() currentTime := time.Now() - subnetID := ids.GenerateTestID() startTime := currentTime s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) currentTime = currentTime.Add(time.Second) clk.Set(currentTime) - uptime, err := up.CalculateUptimePercentFrom(nodeID0, subnetID, startTime.Truncate(time.Second)) + uptime, err := up.CalculateUptimePercentFrom(nodeID0, startTime.Truncate(time.Second)) require.NoError(err) require.Equal(float64(0), uptime) } @@ -592,24 +548,23 @@ func TestStopTrackingUnixTimeRegression(t *testing.T) { nodeID0 := ids.GenerateTestNodeID() currentTime := time.Now() - subnetID := ids.GenerateTestID() startTime := currentTime s := NewTestState() - s.AddNode(nodeID0, subnetID, startTime) + s.AddNode(nodeID0, startTime) clk := mockable.Clock{} up := NewManager(s, &clk) clk.Set(currentTime) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) - require.NoError(up.Connect(nodeID0, subnetID)) + require.NoError(up.Connect(nodeID0)) currentTime = startTime.Add(time.Second) clk.Set(currentTime) - require.NoError(up.StopTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StopTracking([]ids.NodeID{nodeID0})) currentTime = startTime.Add(time.Second) clk.Set(currentTime) @@ -619,14 +574,14 @@ func TestStopTrackingUnixTimeRegression(t *testing.T) { currentTime = startTime.Add(time.Second) clk.Set(currentTime) - require.NoError(up.StartTracking([]ids.NodeID{nodeID0}, subnetID)) + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) - require.NoError(up.Connect(nodeID0, subnetID)) + require.NoError(up.Connect(nodeID0)) currentTime = startTime.Add(time.Second) clk.Set(currentTime) - perc, err := up.CalculateUptimePercent(nodeID0, subnetID) + perc, err := up.CalculateUptimePercent(nodeID0) require.NoError(err) require.GreaterOrEqual(float64(1), perc) } diff --git a/snow/uptime/no_op_calculator.go b/snow/uptime/no_op_calculator.go index fb308f4f6030..2f715d799e70 100644 --- a/snow/uptime/no_op_calculator.go +++ b/snow/uptime/no_op_calculator.go @@ -13,14 +13,14 @@ var NoOpCalculator Calculator = noOpCalculator{} type noOpCalculator struct{} -func (noOpCalculator) CalculateUptime(ids.NodeID, ids.ID) (time.Duration, time.Time, error) { +func (noOpCalculator) CalculateUptime(ids.NodeID) (time.Duration, time.Time, error) { return 0, time.Time{}, nil } -func (noOpCalculator) CalculateUptimePercent(ids.NodeID, ids.ID) (float64, error) { +func (noOpCalculator) CalculateUptimePercent(ids.NodeID) (float64, error) { return 0, nil } -func (noOpCalculator) CalculateUptimePercentFrom(ids.NodeID, ids.ID, time.Time) (float64, error) { +func (noOpCalculator) CalculateUptimePercentFrom(ids.NodeID, time.Time) (float64, error) { return 0, nil } diff --git a/snow/uptime/state.go b/snow/uptime/state.go index f9edeb76a3ee..59a720c897b2 100644 --- a/snow/uptime/state.go +++ b/snow/uptime/state.go @@ -10,34 +10,25 @@ import ( ) type State interface { - // GetUptime returns [upDuration] and [lastUpdated] of [nodeID] on - // [subnetID]. - // Returns [database.ErrNotFound] if [nodeID] isn't currently a validator of - // the subnet. + // GetUptime returns [upDuration] and [lastUpdated] of [nodeID] + // Returns [database.ErrNotFound] if [nodeID] isn't currently a validator. GetUptime( nodeID ids.NodeID, - subnetID ids.ID, ) (upDuration time.Duration, lastUpdated time.Time, err error) - // SetUptime updates [upDuration] and [lastUpdated] of [nodeID] on - // [subnetID]. - // Returns [database.ErrNotFound] if [nodeID] isn't currently a validator of - // the subnet. + // SetUptime updates [upDuration] and [lastUpdated] of [nodeID] + // Returns [database.ErrNotFound] if [nodeID] isn't currently a validator // Invariant: expects [lastUpdated] to be truncated (floored) to the nearest // second. SetUptime( nodeID ids.NodeID, - subnetID ids.ID, upDuration time.Duration, lastUpdated time.Time, ) error - // GetStartTime returns the time that [nodeID] started validating - // [subnetID]. - // Returns [database.ErrNotFound] if [nodeID] isn't currently a validator of - // the subnet. + // GetStartTime returns the time that [nodeID] started validating. + // Returns [database.ErrNotFound] if [nodeID] isn't currently a validator. GetStartTime( nodeID ids.NodeID, - subnetID ids.ID, ) (startTime time.Time, err error) } diff --git a/snow/uptime/test_state.go b/snow/uptime/test_state.go index 23879b5cb3a9..213b591bc71c 100644 --- a/snow/uptime/test_state.go +++ b/snow/uptime/test_state.go @@ -21,38 +21,33 @@ type uptime struct { type TestState struct { dbReadError error dbWriteError error - nodes map[ids.NodeID]map[ids.ID]*uptime + nodes map[ids.NodeID]*uptime } func NewTestState() *TestState { return &TestState{ - nodes: make(map[ids.NodeID]map[ids.ID]*uptime), + nodes: make(map[ids.NodeID]*uptime), } } -func (s *TestState) AddNode(nodeID ids.NodeID, subnetID ids.ID, startTime time.Time) { - subnetUptimes, ok := s.nodes[nodeID] - if !ok { - subnetUptimes = make(map[ids.ID]*uptime) - s.nodes[nodeID] = subnetUptimes - } +func (s *TestState) AddNode(nodeID ids.NodeID, startTime time.Time) { st := time.Unix(startTime.Unix(), 0) - subnetUptimes[subnetID] = &uptime{ + s.nodes[nodeID] = &uptime{ lastUpdated: st, startTime: st, } } -func (s *TestState) GetUptime(nodeID ids.NodeID, subnetID ids.ID) (time.Duration, time.Time, error) { - up, exists := s.nodes[nodeID][subnetID] +func (s *TestState) GetUptime(nodeID ids.NodeID) (time.Duration, time.Time, error) { + up, exists := s.nodes[nodeID] if !exists { return 0, time.Time{}, database.ErrNotFound } return up.upDuration, up.lastUpdated, s.dbReadError } -func (s *TestState) SetUptime(nodeID ids.NodeID, subnetID ids.ID, upDuration time.Duration, lastUpdated time.Time) error { - up, exists := s.nodes[nodeID][subnetID] +func (s *TestState) SetUptime(nodeID ids.NodeID, upDuration time.Duration, lastUpdated time.Time) error { + up, exists := s.nodes[nodeID] if !exists { return database.ErrNotFound } @@ -61,8 +56,8 @@ func (s *TestState) SetUptime(nodeID ids.NodeID, subnetID ids.ID, upDuration tim return s.dbWriteError } -func (s *TestState) GetStartTime(nodeID ids.NodeID, subnetID ids.ID) (time.Time, error) { - up, exists := s.nodes[nodeID][subnetID] +func (s *TestState) GetStartTime(nodeID ids.NodeID) (time.Time, error) { + up, exists := s.nodes[nodeID] if !exists { return time.Time{}, database.ErrNotFound } From 733d5fd3370d3c6ad736df488404cd5c3b024508 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 25 Jul 2024 21:04:58 +0300 Subject: [PATCH 002/199] remove subnet uptimes from platforvm --- vms/platformvm/api/static_service.go | 4 +- vms/platformvm/block/builder/helpers_test.go | 2 +- vms/platformvm/block/executor/block_test.go | 6 +-- vms/platformvm/block/executor/helpers_test.go | 2 +- vms/platformvm/block/executor/options.go | 2 - .../block/executor/proposal_block_test.go | 4 +- .../client_permissionless_validator.go | 2 +- vms/platformvm/service.go | 45 ++++++++++--------- vms/platformvm/state/mock_state.go | 24 +++++----- vms/platformvm/state/state.go | 12 ++++- vms/platformvm/state/state_test.go | 33 +------------- vms/platformvm/txs/executor/helpers_test.go | 4 +- vms/platformvm/vm.go | 20 ++------- 13 files changed, 63 insertions(+), 97 deletions(-) diff --git a/vms/platformvm/api/static_service.go b/vms/platformvm/api/static_service.go index 418b447114c7..326e5fb5e8a8 100644 --- a/vms/platformvm/api/static_service.go +++ b/vms/platformvm/api/static_service.go @@ -121,7 +121,7 @@ type PermissionlessValidator struct { DelegationFee json.Float32 `json:"delegationFee"` ExactDelegationFee *json.Uint32 `json:"exactDelegationFee,omitempty"` Uptime *json.Float32 `json:"uptime,omitempty"` - Connected bool `json:"connected"` + Connected *bool `json:"connected,omitempty"` Staked []UTXO `json:"staked,omitempty"` Signer *signer.ProofOfPossession `json:"signer,omitempty"` @@ -145,7 +145,7 @@ type GenesisPermissionlessValidator struct { type PermissionedValidator struct { Staker // The owner the staking reward, if applicable, will go to - Connected bool `json:"connected"` + Connected *bool `json:"connected,omitempty"` Uptime *json.Float32 `json:"uptime,omitempty"` } diff --git a/vms/platformvm/block/builder/helpers_test.go b/vms/platformvm/block/builder/helpers_test.go index df2d620c6eed..b492a8fdac13 100644 --- a/vms/platformvm/block/builder/helpers_test.go +++ b/vms/platformvm/block/builder/helpers_test.go @@ -221,7 +221,7 @@ func newEnvironment(t *testing.T, f fork) *environment { //nolint:unparam if res.isBootstrapped.Get() { validatorIDs := res.config.Validators.GetValidatorIDs(constants.PrimaryNetworkID) - require.NoError(res.uptimes.StopTracking(validatorIDs, constants.PrimaryNetworkID)) + require.NoError(res.uptimes.StopTracking(validatorIDs)) require.NoError(res.state.Commit()) } diff --git a/vms/platformvm/block/executor/block_test.go b/vms/platformvm/block/executor/block_test.go index dfccc413e29b..bf020e164629 100644 --- a/vms/platformvm/block/executor/block_test.go +++ b/vms/platformvm/block/executor/block_test.go @@ -287,7 +287,7 @@ func TestBlockOptions(t *testing.T) { state.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, nodeID).Return(staker, nil) uptimes := uptime.NewMockCalculator(ctrl) - uptimes.EXPECT().CalculateUptimePercentFrom(nodeID, constants.PrimaryNetworkID, primaryNetworkValidatorStartTime).Return(0.0, database.ErrNotFound) + uptimes.EXPECT().CalculateUptimePercentFrom(nodeID, primaryNetworkValidatorStartTime).Return(0.0, database.ErrNotFound) manager := &manager{ backend: &backend{ @@ -405,7 +405,7 @@ func TestBlockOptions(t *testing.T) { state.EXPECT().GetSubnetTransformation(subnetID).Return(transformSubnetTx, nil) uptimes := uptime.NewMockCalculator(ctrl) - uptimes.EXPECT().CalculateUptimePercentFrom(nodeID, constants.PrimaryNetworkID, primaryNetworkValidatorStartTime).Return(.5, nil) + uptimes.EXPECT().CalculateUptimePercentFrom(nodeID, primaryNetworkValidatorStartTime).Return(.5, nil) manager := &manager{ backend: &backend{ @@ -467,7 +467,7 @@ func TestBlockOptions(t *testing.T) { state.EXPECT().GetSubnetTransformation(subnetID).Return(transformSubnetTx, nil) uptimes := uptime.NewMockCalculator(ctrl) - uptimes.EXPECT().CalculateUptimePercentFrom(nodeID, constants.PrimaryNetworkID, primaryNetworkValidatorStartTime).Return(.5, nil) + uptimes.EXPECT().CalculateUptimePercentFrom(nodeID, primaryNetworkValidatorStartTime).Return(.5, nil) manager := &manager{ backend: &backend{ diff --git a/vms/platformvm/block/executor/helpers_test.go b/vms/platformvm/block/executor/helpers_test.go index 80dca6745fcc..b23a34b8264b 100644 --- a/vms/platformvm/block/executor/helpers_test.go +++ b/vms/platformvm/block/executor/helpers_test.go @@ -237,7 +237,7 @@ func newEnvironment(t *testing.T, ctrl *gomock.Controller, f fork) *environment if res.isBootstrapped.Get() { validatorIDs := res.config.Validators.GetValidatorIDs(constants.PrimaryNetworkID) - require.NoError(res.uptimes.StopTracking(validatorIDs, constants.PrimaryNetworkID)) + require.NoError(res.uptimes.StopTracking(validatorIDs)) require.NoError(res.state.Commit()) } diff --git a/vms/platformvm/block/executor/options.go b/vms/platformvm/block/executor/options.go index f2071c8e13cb..7487ea449335 100644 --- a/vms/platformvm/block/executor/options.go +++ b/vms/platformvm/block/executor/options.go @@ -175,10 +175,8 @@ func (o *options) prefersCommit(tx *txs.Tx) (bool, error) { expectedUptimePercentage = float64(transformSubnet.UptimeRequirement) / reward.PercentDenominator } - // TODO: calculate subnet uptimes uptime, err := o.uptimes.CalculateUptimePercentFrom( nodeID, - constants.PrimaryNetworkID, primaryNetworkValidator.StartTime, ) if err != nil { diff --git a/vms/platformvm/block/executor/proposal_block_test.go b/vms/platformvm/block/executor/proposal_block_test.go index f0037754d06a..0b914aff4c98 100644 --- a/vms/platformvm/block/executor/proposal_block_test.go +++ b/vms/platformvm/block/executor/proposal_block_test.go @@ -105,7 +105,7 @@ func TestApricotProposalBlockTimeVerification(t *testing.T) { onParentAccept.EXPECT().GetCurrentSupply(constants.PrimaryNetworkID).Return(uint64(1000), nil).AnyTimes() onParentAccept.EXPECT().GetDelegateeReward(constants.PrimaryNetworkID, utx.NodeID()).Return(uint64(0), nil).AnyTimes() - env.mockedState.EXPECT().GetUptime(gomock.Any(), constants.PrimaryNetworkID).Return( + env.mockedState.EXPECT().GetUptime(gomock.Any()).Return( time.Microsecond, /*upDuration*/ time.Time{}, /*lastUpdated*/ nil, /*err*/ @@ -219,7 +219,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { pendingStakersIt.EXPECT().Release().AnyTimes() onParentAccept.EXPECT().GetPendingStakerIterator().Return(pendingStakersIt, nil).AnyTimes() - env.mockedState.EXPECT().GetUptime(gomock.Any(), gomock.Any()).Return( + env.mockedState.EXPECT().GetUptime(gomock.Any).Return( time.Microsecond, /*upDuration*/ time.Time{}, /*lastUpdated*/ nil, /*err*/ diff --git a/vms/platformvm/client_permissionless_validator.go b/vms/platformvm/client_permissionless_validator.go index 3974f770658d..e9cdfa6ccbeb 100644 --- a/vms/platformvm/client_permissionless_validator.go +++ b/vms/platformvm/client_permissionless_validator.go @@ -133,7 +133,7 @@ func getClientPermissionlessValidators(validatorsSliceIntf []interface{}) ([]Cli AccruedDelegateeReward: (*uint64)(apiValidator.AccruedDelegateeReward), DelegationFee: float32(apiValidator.DelegationFee), Uptime: (*float32)(apiValidator.Uptime), - Connected: &apiValidator.Connected, + Connected: apiValidator.Connected, Signer: apiValidator.Signer, DelegatorCount: (*uint64)(apiValidator.DelegatorCount), DelegatorWeight: (*uint64)(apiValidator.DelegatorWeight), diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 0299192b6677..1bb82e1a1203 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -830,13 +830,20 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato shares := attr.shares delegationFee := avajson.Float32(100 * float32(shares) / float32(reward.PercentDenominator)) - - uptime, err := s.getAPIUptime(currentStaker) - if err != nil { - return err + var uptime *avajson.Float32 + var connected *bool + // Only calculate uptime for primary network validators + // TODO: decide whether we want to keep connected for subnet validators + // it should be available at this point + if args.SubnetID == constants.PrimaryNetworkID { + currentUptime, isConnected, err := s.getAPIUptime(currentStaker) + if err != nil { + return err + } + connected = &isConnected + uptime = ¤tUptime } - connected := s.vm.uptimeManager.IsConnected(nodeID, args.SubnetID) var ( validationRewardOwner *platformapi.Owner delegationRewardOwner *platformapi.Owner @@ -896,15 +903,8 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato vdrToDelegators[delegator.NodeID] = append(vdrToDelegators[delegator.NodeID], delegator) case txs.SubnetPermissionedValidatorCurrentPriority: - uptime, err := s.getAPIUptime(currentStaker) - if err != nil { - return err - } - connected := s.vm.uptimeManager.IsConnected(nodeID, args.SubnetID) reply.Validators = append(reply.Validators, platformapi.PermissionedValidator{ - Staker: apiStaker, - Connected: connected, - Uptime: uptime, + Staker: apiStaker, }) default: @@ -1828,20 +1828,21 @@ func (s *Service) GetBlockByHeight(_ *http.Request, args *api.GetBlockByHeightAr return err } -func (s *Service) getAPIUptime(staker *state.Staker) (*avajson.Float32, error) { - // Only report uptimes that we have been actively tracking. - if constants.PrimaryNetworkID != staker.SubnetID && !s.vm.TrackedSubnets.Contains(staker.SubnetID) { - return nil, nil - } - - rawUptime, err := s.vm.uptimeManager.CalculateUptimePercentFrom(staker.NodeID, staker.SubnetID, staker.StartTime) +// Returns: +// 1) the uptime of a validator in the API format +// 2) whether the validator is currently connected +// 3) an error if one occurred +func (s *Service) getAPIUptime(staker *state.Staker) (avajson.Float32, bool, error) { + rawUptime, err := s.vm.uptimeManager.CalculateUptimePercentFrom(staker.NodeID, staker.StartTime) if err != nil { - return nil, err + return 0, false, err } + connected := s.vm.uptimeManager.IsConnected(staker.NodeID) + // Transform this to a percentage (0-100) to make it consistent // with observedUptime in info.peers API uptime := avajson.Float32(rawUptime * 100) - return &uptime, nil + return uptime, connected, nil } func (s *Service) getAPIOwner(owner *secp256k1fx.OutputOwners) (*platformapi.Owner, error) { diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index c1321567e6a9..36087cd934e6 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -1381,18 +1381,18 @@ func (mr *MockStateMockRecorder) GetRewardUTXOs(arg0 any) *gomock.Call { } // GetStartTime mocks base method. -func (m *MockState) GetStartTime(arg0 ids.NodeID, arg1 ids.ID) (time.Time, error) { +func (m *MockState) GetStartTime(arg0 ids.NodeID) (time.Time, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetStartTime", arg0, arg1) + ret := m.ctrl.Call(m, "GetStartTime", arg0) ret0, _ := ret[0].(time.Time) ret1, _ := ret[1].(error) return ret0, ret1 } // GetStartTime indicates an expected call of GetStartTime. -func (mr *MockStateMockRecorder) GetStartTime(arg0, arg1 any) *gomock.Call { +func (mr *MockStateMockRecorder) GetStartTime(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStartTime", reflect.TypeOf((*MockState)(nil).GetStartTime), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStartTime", reflect.TypeOf((*MockState)(nil).GetStartTime), arg0) } // GetStatelessBlock mocks base method. @@ -1501,9 +1501,9 @@ func (mr *MockStateMockRecorder) GetUTXO(arg0 any) *gomock.Call { } // GetUptime mocks base method. -func (m *MockState) GetUptime(arg0 ids.NodeID, arg1 ids.ID) (time.Duration, time.Time, error) { +func (m *MockState) GetUptime(arg0 ids.NodeID) (time.Duration, time.Time, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUptime", arg0, arg1) + ret := m.ctrl.Call(m, "GetUptime", arg0) ret0, _ := ret[0].(time.Duration) ret1, _ := ret[1].(time.Time) ret2, _ := ret[2].(error) @@ -1511,9 +1511,9 @@ func (m *MockState) GetUptime(arg0 ids.NodeID, arg1 ids.ID) (time.Duration, time } // GetUptime indicates an expected call of GetUptime. -func (mr *MockStateMockRecorder) GetUptime(arg0, arg1 any) *gomock.Call { +func (mr *MockStateMockRecorder) GetUptime(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUptime", reflect.TypeOf((*MockState)(nil).GetUptime), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUptime", reflect.TypeOf((*MockState)(nil).GetUptime), arg0) } // PutCurrentDelegator mocks base method. @@ -1653,17 +1653,17 @@ func (mr *MockStateMockRecorder) SetTimestamp(arg0 any) *gomock.Call { } // SetUptime mocks base method. -func (m *MockState) SetUptime(arg0 ids.NodeID, arg1 ids.ID, arg2 time.Duration, arg3 time.Time) error { +func (m *MockState) SetUptime(arg0 ids.NodeID, arg1 time.Duration, arg2 time.Time) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetUptime", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "SetUptime", arg0, arg1, arg2) ret0, _ := ret[0].(error) return ret0 } // SetUptime indicates an expected call of SetUptime. -func (mr *MockStateMockRecorder) SetUptime(arg0, arg1, arg2, arg3 any) *gomock.Call { +func (mr *MockStateMockRecorder) SetUptime(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUptime", reflect.TypeOf((*MockState)(nil).SetUptime), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUptime", reflect.TypeOf((*MockState)(nil).SetUptime), arg0, arg1, arg2) } // UTXOIDs mocks base method. diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index a6266a480858..d8e9ed3ea3bc 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -989,8 +989,8 @@ func (s *state) DeleteUTXO(utxoID ids.ID) { s.modifiedUTXOs[utxoID] = nil } -func (s *state) GetStartTime(nodeID ids.NodeID, subnetID ids.ID) (time.Time, error) { - staker, err := s.currentStakers.GetValidator(subnetID, nodeID) +func (s *state) GetStartTime(nodeID ids.NodeID) (time.Time, error) { + staker, err := s.currentStakers.GetValidator(constants.PrimaryNetworkID, nodeID) if err != nil { return time.Time{}, err } @@ -2440,3 +2440,11 @@ func (s *state) ReindexBlocks(lock sync.Locker, log logging.Logger) error { return s.Commit() } + +func (s *state) GetUptime(vdrID ids.NodeID) (time.Duration, time.Time, error) { + return s.validatorState.GetUptime(vdrID, constants.PrimaryNetworkID) +} + +func (s *state) SetUptime(vdrID ids.NodeID, upDuration time.Duration, lastUpdated time.Time) error { + return s.validatorState.SetUptime(vdrID, constants.PrimaryNetworkID, upDuration, lastUpdated) +} diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index c6241ddc8cc4..517981552042 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -106,9 +106,6 @@ func TestPersistStakers(t *testing.T) { // with the right weight and showing the BLS key checkValidatorsSet func(*require.Assertions, *state, *Staker) - // Check that node duly track stakers uptimes - checkValidatorUptimes func(*require.Assertions, *state, *Staker) - // Check whether weight/bls keys diffs are duly stored checkDiffs func(*require.Assertions, *state, *Staker, uint64) }{ @@ -159,12 +156,6 @@ func TestPersistStakers(t *testing.T) { Weight: staker.Weight, }, valOut) }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - upDuration, lastUpdated, err := s.GetUptime(staker.NodeID, staker.SubnetID) - r.NoError(err) - r.Equal(upDuration, time.Duration(0)) - r.Equal(lastUpdated, staker.StartTime) - }, checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) r.NoError(err) @@ -261,7 +252,6 @@ func TestPersistStakers(t *testing.T) { r.Equal(valOut.NodeID, staker.NodeID) r.Equal(valOut.Weight, val.Weight+staker.Weight) }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { // validator's weight must increase of delegator's weight amount weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) @@ -313,11 +303,6 @@ func TestPersistStakers(t *testing.T) { valsMap := s.cfg.Validators.GetMap(staker.SubnetID) r.Empty(valsMap) }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - // pending validators uptime is not tracked - _, _, err := s.GetUptime(staker.NodeID, staker.SubnetID) - r.ErrorIs(err, database.ErrNotFound) - }, checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { // pending validators weight diff and bls diffs are not stored _, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) @@ -388,8 +373,7 @@ func TestPersistStakers(t *testing.T) { valsMap := s.cfg.Validators.GetMap(staker.SubnetID) r.Empty(valsMap) }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, + checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, }, "delete current validator": { storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { @@ -435,11 +419,6 @@ func TestPersistStakers(t *testing.T) { valsMap := s.cfg.Validators.GetMap(staker.SubnetID) r.Empty(valsMap) }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - // uptimes of delete validators are dropped - _, _, err := s.GetUptime(staker.NodeID, staker.SubnetID) - r.ErrorIs(err, database.ErrNotFound) - }, checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) r.NoError(err) @@ -536,7 +515,6 @@ func TestPersistStakers(t *testing.T) { r.Equal(valOut.NodeID, staker.NodeID) r.Equal(valOut.Weight, val.Weight) }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { // validator's weight must decrease of delegator's weight amount weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) @@ -590,10 +568,6 @@ func TestPersistStakers(t *testing.T) { valsMap := s.cfg.Validators.GetMap(staker.SubnetID) r.Empty(valsMap) }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - _, _, err := s.GetUptime(staker.NodeID, staker.SubnetID) - r.ErrorIs(err, database.ErrNotFound) - }, checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { _, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) r.ErrorIs(err, database.ErrNotFound) @@ -661,8 +635,7 @@ func TestPersistStakers(t *testing.T) { valsMap := s.cfg.Validators.GetMap(staker.SubnetID) r.Empty(valsMap) }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, + checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, }, } @@ -680,7 +653,6 @@ func TestPersistStakers(t *testing.T) { // check all relevant data are stored test.checkStakerInState(require, state, staker) test.checkValidatorsSet(require, state, staker) - test.checkValidatorUptimes(require, state, staker) test.checkDiffs(require, state, staker, 0 /*height*/) // rebuild the state @@ -694,7 +666,6 @@ func TestPersistStakers(t *testing.T) { // check again that all relevant data are still available in rebuilt state test.checkStakerInState(require, state, staker) test.checkValidatorsSet(require, state, staker) - test.checkValidatorUptimes(require, state, staker) test.checkDiffs(require, state, staker, 0 /*height*/) }) } diff --git a/vms/platformvm/txs/executor/helpers_test.go b/vms/platformvm/txs/executor/helpers_test.go index a3b3033df44a..69321d539a65 100644 --- a/vms/platformvm/txs/executor/helpers_test.go +++ b/vms/platformvm/txs/executor/helpers_test.go @@ -187,12 +187,12 @@ func newEnvironment(t *testing.T, f fork) *environment { if env.isBootstrapped.Get() { validatorIDs := env.config.Validators.GetValidatorIDs(constants.PrimaryNetworkID) - require.NoError(env.uptimes.StopTracking(validatorIDs, constants.PrimaryNetworkID)) + require.NoError(env.uptimes.StopTracking(validatorIDs)) for subnetID := range env.config.TrackedSubnets { validatorIDs := env.config.Validators.GetValidatorIDs(subnetID) - require.NoError(env.uptimes.StopTracking(validatorIDs, subnetID)) + require.NoError(env.uptimes.StopTracking(validatorIDs)) } env.state.SetHeight(math.MaxUint64) require.NoError(env.state.Commit()) diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index 203688d23136..6690488ef291 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -349,7 +349,7 @@ func (vm *VM) onNormalOperationsStarted() error { } primaryVdrIDs := vm.Validators.GetValidatorIDs(constants.PrimaryNetworkID) - if err := vm.uptimeManager.StartTracking(primaryVdrIDs, constants.PrimaryNetworkID); err != nil { + if err := vm.uptimeManager.StartTracking(primaryVdrIDs); err != nil { return err } @@ -357,11 +357,6 @@ func (vm *VM) onNormalOperationsStarted() error { vm.Validators.RegisterSetCallbackListener(constants.PrimaryNetworkID, vl) for subnetID := range vm.TrackedSubnets { - vdrIDs := vm.Validators.GetValidatorIDs(subnetID) - if err := vm.uptimeManager.StartTracking(vdrIDs, subnetID); err != nil { - return err - } - vl := validators.NewLogger(vm.ctx.Log, subnetID, vm.ctx.NodeID) vm.Validators.RegisterSetCallbackListener(subnetID, vl) } @@ -397,17 +392,10 @@ func (vm *VM) Shutdown(context.Context) error { if vm.bootstrapped.Get() { primaryVdrIDs := vm.Validators.GetValidatorIDs(constants.PrimaryNetworkID) - if err := vm.uptimeManager.StopTracking(primaryVdrIDs, constants.PrimaryNetworkID); err != nil { + if err := vm.uptimeManager.StopTracking(primaryVdrIDs); err != nil { return err } - for subnetID := range vm.TrackedSubnets { - vdrIDs := vm.Validators.GetValidatorIDs(subnetID) - if err := vm.uptimeManager.StopTracking(vdrIDs, subnetID); err != nil { - return err - } - } - if err := vm.state.Commit(); err != nil { return err } @@ -473,14 +461,14 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) { } func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, version *version.Application) error { - if err := vm.uptimeManager.Connect(nodeID, constants.PrimaryNetworkID); err != nil { + if err := vm.uptimeManager.Connect(nodeID); err != nil { return err } return vm.Network.Connected(ctx, nodeID, version) } func (vm *VM) ConnectedSubnet(_ context.Context, nodeID ids.NodeID, subnetID ids.ID) error { - return vm.uptimeManager.Connect(nodeID, subnetID) + return nil } func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error { From 10d8e868affdac0a0f5319718975a74d817ccc7e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 25 Jul 2024 21:05:35 +0300 Subject: [PATCH 003/199] remove subnet uptimes from p2p --- message/messages_test.go | 11 +- message/mock_outbound_message_builder.go | 8 +- message/outbound_msg_builder.go | 5 +- network/metrics.go | 52 +- network/network.go | 39 +- network/peer/info.go | 21 +- network/peer/message_queue_test.go | 11 +- network/peer/peer.go | 136 +--- network/peer/peer_test.go | 126 +--- network/peer/set_test.go | 22 +- proto/p2p/p2p.proto | 11 +- proto/pb/p2p/p2p.pb.go | 833 ++++++++++------------- 12 files changed, 469 insertions(+), 806 deletions(-) diff --git a/message/messages_test.go b/message/messages_test.go index 8314fa805e38..7d4ace78b5bc 100644 --- a/message/messages_test.go +++ b/message/messages_test.go @@ -54,7 +54,7 @@ func TestMessage(t *testing.T) { bytesSaved bool // if true, outbound message saved bytes must be non-zero }{ { - desc: "ping message with no compression no subnet uptimes", + desc: "ping message with no compression no uptime", op: PingOp, msg: &p2p.Message{ Message: &p2p.Message_Ping{ @@ -78,17 +78,12 @@ func TestMessage(t *testing.T) { bytesSaved: false, }, { - desc: "ping message with no compression and subnet uptimes", + desc: "ping message with no compression and uptime", op: PingOp, msg: &p2p.Message{ Message: &p2p.Message_Ping{ Ping: &p2p.Ping{ - SubnetUptimes: []*p2p.SubnetUptime{ - { - SubnetId: testID[:], - Uptime: 100, - }, - }, + Uptime: 100, }, }, }, diff --git a/message/mock_outbound_message_builder.go b/message/mock_outbound_message_builder.go index 917d764028fe..c480ca1d1c65 100644 --- a/message/mock_outbound_message_builder.go +++ b/message/mock_outbound_message_builder.go @@ -314,18 +314,18 @@ func (mr *MockOutboundMsgBuilderMockRecorder) PeerList(arg0, arg1 any) *gomock.C } // Ping mocks base method. -func (m *MockOutboundMsgBuilder) Ping(arg0 uint32, arg1 []*p2p.SubnetUptime) (OutboundMessage, error) { +func (m *MockOutboundMsgBuilder) Ping(arg0 uint32) (OutboundMessage, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Ping", arg0, arg1) + ret := m.ctrl.Call(m, "Ping", arg0) ret0, _ := ret[0].(OutboundMessage) ret1, _ := ret[1].(error) return ret0, ret1 } // Ping indicates an expected call of Ping. -func (mr *MockOutboundMsgBuilderMockRecorder) Ping(arg0, arg1 any) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) Ping(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).Ping), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).Ping), arg0) } // Pong mocks base method. diff --git a/message/outbound_msg_builder.go b/message/outbound_msg_builder.go index 78aacdce3e08..208b04902ae1 100644 --- a/message/outbound_msg_builder.go +++ b/message/outbound_msg_builder.go @@ -49,7 +49,6 @@ type OutboundMsgBuilder interface { Ping( primaryUptime uint32, - subnetUptimes []*p2p.SubnetUptime, ) (OutboundMessage, error) Pong() (OutboundMessage, error) @@ -198,14 +197,12 @@ func newOutboundBuilder(compressionType compression.Type, builder *msgBuilder) O func (b *outMsgBuilder) Ping( primaryUptime uint32, - subnetUptimes []*p2p.SubnetUptime, ) (OutboundMessage, error) { return b.builder.createOutbound( &p2p.Message{ Message: &p2p.Message_Ping{ Ping: &p2p.Ping{ - Uptime: primaryUptime, - SubnetUptimes: subnetUptimes, + Uptime: primaryUptime, }, }, }, diff --git a/network/metrics.go b/network/metrics.go index 9702e821b246..fda3c0bf5f85 100644 --- a/network/metrics.go +++ b/network/metrics.go @@ -19,24 +19,22 @@ type metrics struct { // trackedSubnets does not include the primary network ID trackedSubnets set.Set[ids.ID] - numTracked prometheus.Gauge - numPeers prometheus.Gauge - numSubnetPeers *prometheus.GaugeVec - timeSinceLastMsgSent prometheus.Gauge - timeSinceLastMsgReceived prometheus.Gauge - sendFailRate prometheus.Gauge - connected prometheus.Counter - disconnected prometheus.Counter - acceptFailed prometheus.Counter - inboundConnRateLimited prometheus.Counter - inboundConnAllowed prometheus.Counter - tlsConnRejected prometheus.Counter - numUselessPeerListBytes prometheus.Counter - nodeUptimeWeightedAverage prometheus.Gauge - nodeUptimeRewardingStake prometheus.Gauge - nodeSubnetUptimeWeightedAverage *prometheus.GaugeVec - nodeSubnetUptimeRewardingStake *prometheus.GaugeVec - peerConnectedLifetimeAverage prometheus.Gauge + numTracked prometheus.Gauge + numPeers prometheus.Gauge + numSubnetPeers *prometheus.GaugeVec + timeSinceLastMsgSent prometheus.Gauge + timeSinceLastMsgReceived prometheus.Gauge + sendFailRate prometheus.Gauge + connected prometheus.Counter + disconnected prometheus.Counter + acceptFailed prometheus.Counter + inboundConnRateLimited prometheus.Counter + inboundConnAllowed prometheus.Counter + tlsConnRejected prometheus.Counter + numUselessPeerListBytes prometheus.Counter + nodeUptimeWeightedAverage prometheus.Gauge + nodeUptimeRewardingStake prometheus.Gauge + peerConnectedLifetimeAverage prometheus.Gauge lock sync.RWMutex peerConnectedStartTimes map[ids.NodeID]float64 @@ -112,20 +110,6 @@ func newMetrics( Name: "node_uptime_rewarding_stake", Help: "The percentage of total stake which thinks this node is eligible for rewards", }), - nodeSubnetUptimeWeightedAverage: prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: "node_subnet_uptime_weighted_average", - Help: "This node's subnet uptime averages weighted by observing subnet peer stakes", - }, - []string{"subnetID"}, - ), - nodeSubnetUptimeRewardingStake: prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: "node_subnet_uptime_rewarding_stake", - Help: "The percentage of subnet's total stake which thinks this node is eligible for subnet's rewards", - }, - []string{"subnetID"}, - ), peerConnectedLifetimeAverage: prometheus.NewGauge( prometheus.GaugeOpts{ Name: "peer_connected_duration_average", @@ -151,8 +135,6 @@ func newMetrics( registerer.Register(m.inboundConnRateLimited), registerer.Register(m.nodeUptimeWeightedAverage), registerer.Register(m.nodeUptimeRewardingStake), - registerer.Register(m.nodeSubnetUptimeWeightedAverage), - registerer.Register(m.nodeSubnetUptimeRewardingStake), registerer.Register(m.peerConnectedLifetimeAverage), ) @@ -161,8 +143,6 @@ func newMetrics( // initialize to 0 subnetIDStr := subnetID.String() m.numSubnetPeers.WithLabelValues(subnetIDStr).Set(0) - m.nodeSubnetUptimeWeightedAverage.WithLabelValues(subnetIDStr).Set(0) - m.nodeSubnetUptimeRewardingStake.WithLabelValues(subnetIDStr).Set(0) } return m, err diff --git a/network/network.go b/network/network.go index 2aee13a910d9..e92bc248a218 100644 --- a/network/network.go +++ b/network/network.go @@ -85,9 +85,9 @@ type Network interface { // info about the peers in [nodeIDs] that have finished the handshake. PeerInfo(nodeIDs []ids.NodeID) []peer.Info - // NodeUptime returns given node's [subnetID] UptimeResults in the view of + // NodeUptime returns given node's primary network UptimeResults in the view of // this node's peer validators. - NodeUptime(subnetID ids.ID) (UptimeResult, error) + NodeUptime() (UptimeResult, error) } type UptimeResult struct { @@ -1098,19 +1098,15 @@ func (n *network) StartClose() { }) } -func (n *network) NodeUptime(subnetID ids.ID) (UptimeResult, error) { - if subnetID != constants.PrimaryNetworkID && !n.config.TrackedSubnets.Contains(subnetID) { - return UptimeResult{}, errNotTracked - } - - myStake := n.config.Validators.GetWeight(subnetID, n.config.MyNodeID) +func (n *network) NodeUptime() (UptimeResult, error) { + myStake := n.config.Validators.GetWeight(constants.PrimaryNetworkID, n.config.MyNodeID) if myStake == 0 { return UptimeResult{}, errNotValidator } - totalWeightInt, err := n.config.Validators.TotalWeight(subnetID) + totalWeightInt, err := n.config.Validators.TotalWeight(constants.PrimaryNetworkID) if err != nil { - return UptimeResult{}, fmt.Errorf("error while fetching weight for subnet %s: %w", subnetID, err) + return UptimeResult{}, fmt.Errorf("error while fetching weight for primary network %w", err) } var ( @@ -1126,22 +1122,18 @@ func (n *network) NodeUptime(subnetID ids.ID) (UptimeResult, error) { peer, _ := n.connectedPeers.GetByIndex(i) nodeID := peer.ID() - weight := n.config.Validators.GetWeight(subnetID, nodeID) + weight := n.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID) if weight == 0 { // this is not a validator skip it. continue } - observedUptime, exist := peer.ObservedUptime(subnetID) - if !exist { - observedUptime = 0 - } + observedUptime := peer.ObservedUptime() percent := float64(observedUptime) weightFloat := float64(weight) totalWeightedPercent += percent * weightFloat // if this peer thinks we're above requirement add the weight - // TODO: use subnet-specific uptime requirements if percent/100 >= n.config.UptimeRequirement { rewardingStake += weightFloat } @@ -1177,7 +1169,7 @@ func (n *network) runTimers() { n.peerConfig.Log.Debug("reset ip tracker bloom filter") } case <-updateUptimes.C: - primaryUptime, err := n.NodeUptime(constants.PrimaryNetworkID) + primaryUptime, err := n.NodeUptime() if err != nil { n.peerConfig.Log.Debug("failed to get primary network uptime", zap.Error(err), @@ -1185,19 +1177,6 @@ func (n *network) runTimers() { } n.metrics.nodeUptimeWeightedAverage.Set(primaryUptime.WeightedAveragePercentage) n.metrics.nodeUptimeRewardingStake.Set(primaryUptime.RewardingStakePercentage) - - for subnetID := range n.config.TrackedSubnets { - result, err := n.NodeUptime(subnetID) - if err != nil { - n.peerConfig.Log.Debug("failed to get subnet uptime", - zap.Stringer("subnetID", subnetID), - zap.Error(err), - ) - } - subnetIDStr := subnetID.String() - n.metrics.nodeSubnetUptimeWeightedAverage.WithLabelValues(subnetIDStr).Set(result.WeightedAveragePercentage) - n.metrics.nodeSubnetUptimeRewardingStake.WithLabelValues(subnetIDStr).Set(result.RewardingStakePercentage) - } } } } diff --git a/network/peer/info.go b/network/peer/info.go index 928c47ff26ee..71d2c78420f9 100644 --- a/network/peer/info.go +++ b/network/peer/info.go @@ -13,15 +13,14 @@ import ( ) type Info struct { - IP netip.AddrPort `json:"ip"` - PublicIP netip.AddrPort `json:"publicIP,omitempty"` - ID ids.NodeID `json:"nodeID"` - Version string `json:"version"` - LastSent time.Time `json:"lastSent"` - LastReceived time.Time `json:"lastReceived"` - ObservedUptime json.Uint32 `json:"observedUptime"` - ObservedSubnetUptimes map[ids.ID]json.Uint32 `json:"observedSubnetUptimes"` - TrackedSubnets set.Set[ids.ID] `json:"trackedSubnets"` - SupportedACPs set.Set[uint32] `json:"supportedACPs"` - ObjectedACPs set.Set[uint32] `json:"objectedACPs"` + IP netip.AddrPort `json:"ip"` + PublicIP netip.AddrPort `json:"publicIP,omitempty"` + ID ids.NodeID `json:"nodeID"` + Version string `json:"version"` + LastSent time.Time `json:"lastSent"` + LastReceived time.Time `json:"lastReceived"` + ObservedUptime json.Uint32 `json:"observedUptime"` + TrackedSubnets set.Set[ids.ID] `json:"trackedSubnets"` + SupportedACPs set.Set[uint32] `json:"supportedACPs"` + ObjectedACPs set.Set[uint32] `json:"objectedACPs"` } diff --git a/network/peer/message_queue_test.go b/network/peer/message_queue_test.go index 4b9b63f4c449..a4cce461abab 100644 --- a/network/peer/message_queue_test.go +++ b/network/peer/message_queue_test.go @@ -9,9 +9,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/message" - "github.com/ava-labs/avalanchego/proto/pb/p2p" "github.com/ava-labs/avalanchego/utils/logging" ) @@ -33,14 +31,7 @@ func TestMessageQueue(t *testing.T) { // Assert that the messages are popped in the same order they were pushed for i := 0; i < numToSend; i++ { - testID := ids.GenerateTestID() - testID2 := ids.GenerateTestID() - m, err := mc.Ping( - uint32(i), - []*p2p.SubnetUptime{ - {SubnetId: testID[:], Uptime: uint32(i)}, - {SubnetId: testID2[:], Uptime: uint32(i)}, - }) + m, err := mc.Ping(uint32(i)) require.NoError(err) msgs = append(msgs, m) } diff --git a/network/peer/peer.go b/network/peer/peer.go index a92791ff72ee..e3161cfc0eab 100644 --- a/network/peer/peer.go +++ b/network/peer/peer.go @@ -94,10 +94,10 @@ type Peer interface { // be called after [Ready] returns true. TrackedSubnets() set.Set[ids.ID] - // ObservedUptime returns the local node's subnet uptime according to the + // ObservedUptime returns the local node's primary network uptime according to the // peer. The value ranges from [0, 100]. It should only be called after // [Ready] returns true. - ObservedUptime(subnetID ids.ID) (uint32, bool) + ObservedUptime() uint32 // Send attempts to send [msg] to the peer. The peer takes ownership of // [msg] for reference counting. This returns false if the message is @@ -158,10 +158,8 @@ type peer struct { // this can only be accessed by the message sender goroutine. txIDOfVerifiedBLSKey ids.ID - observedUptimesLock sync.RWMutex - // [observedUptimesLock] must be held while accessing [observedUptime] - // Subnet ID --> Our uptime for the given subnet as perceived by the peer - observedUptimes map[ids.ID]uint32 + // Our primary network uptime perceived by the peer + observedUptime utils.Atomic[uint32] // True if this peer has sent us a valid Handshake message and // is running a compatible version. @@ -221,7 +219,6 @@ func Start( onClosingCtx: onClosingCtx, onClosingCtxCancel: onClosingCtxCancel, onClosed: make(chan struct{}), - observedUptimes: make(map[ids.ID]uint32), getPeerListChan: make(chan struct{}, 1), } @@ -270,33 +267,20 @@ func (p *peer) AwaitReady(ctx context.Context) error { } func (p *peer) Info() Info { - uptimes := make(map[ids.ID]json.Uint32, p.MySubnets.Len()) - for subnetID := range p.MySubnets { - uptime, exist := p.ObservedUptime(subnetID) - if !exist { - continue - } - uptimes[subnetID] = json.Uint32(uptime) - } - - primaryUptime, exist := p.ObservedUptime(constants.PrimaryNetworkID) - if !exist { - primaryUptime = 0 - } + primaryUptime := p.ObservedUptime() ip, _ := ips.ParseAddrPort(p.conn.RemoteAddr().String()) return Info{ - IP: ip, - PublicIP: p.ip.AddrPort, - ID: p.id, - Version: p.version.String(), - LastSent: p.LastSent(), - LastReceived: p.LastReceived(), - ObservedUptime: json.Uint32(primaryUptime), - ObservedSubnetUptimes: uptimes, - TrackedSubnets: p.trackedSubnets, - SupportedACPs: p.supportedACPs, - ObjectedACPs: p.objectedACPs, + IP: ip, + PublicIP: p.ip.AddrPort, + ID: p.id, + Version: p.version.String(), + LastSent: p.LastSent(), + LastReceived: p.LastReceived(), + ObservedUptime: json.Uint32(primaryUptime), + TrackedSubnets: p.trackedSubnets, + SupportedACPs: p.supportedACPs, + ObjectedACPs: p.objectedACPs, } } @@ -312,12 +296,8 @@ func (p *peer) TrackedSubnets() set.Set[ids.ID] { return p.trackedSubnets } -func (p *peer) ObservedUptime(subnetID ids.ID) (uint32, bool) { - p.observedUptimesLock.RLock() - defer p.observedUptimesLock.RUnlock() - - uptime, exist := p.observedUptimes[subnetID] - return uptime, exist +func (p *peer) ObservedUptime() uint32 { + return p.observedUptime.Get() } func (p *peer) Send(ctx context.Context, msg message.OutboundMessage) bool { @@ -670,8 +650,8 @@ func (p *peer) sendNetworkMessages() { return } - primaryUptime, subnetUptimes := p.getUptimes() - pingMessage, err := p.MessageCreator.Ping(primaryUptime, subnetUptimes) + primaryUptime := p.getUptime() + pingMessage, err := p.MessageCreator.Ping(primaryUptime) if err != nil { p.Log.Error(failedToCreateMessageLog, zap.Stringer("nodeID", p.id), @@ -782,45 +762,7 @@ func (p *peer) handlePing(msg *p2p.Ping) { p.StartClose() return } - p.observeUptime(constants.PrimaryNetworkID, msg.Uptime) - - for _, subnetUptime := range msg.SubnetUptimes { - subnetID, err := ids.ToID(subnetUptime.SubnetId) - if err != nil { - p.Log.Debug(malformedMessageLog, - zap.Stringer("nodeID", p.id), - zap.Stringer("messageOp", message.PingOp), - zap.String("field", "subnetID"), - zap.Error(err), - ) - p.StartClose() - return - } - - if !p.MySubnets.Contains(subnetID) { - p.Log.Debug(malformedMessageLog, - zap.Stringer("nodeID", p.id), - zap.Stringer("messageOp", message.PingOp), - zap.Stringer("subnetID", subnetID), - zap.String("reason", "not tracking subnet"), - ) - p.StartClose() - return - } - - uptime := subnetUptime.Uptime - if uptime > 100 { - p.Log.Debug(malformedMessageLog, - zap.Stringer("nodeID", p.id), - zap.Stringer("messageOp", message.PingOp), - zap.Stringer("subnetID", subnetID), - zap.Uint32("uptime", uptime), - ) - p.StartClose() - return - } - p.observeUptime(subnetID, uptime) - } + p.observedUptime.Set(msg.Uptime) pongMessage, err := p.MessageCreator.Pong() if err != nil { @@ -836,10 +778,9 @@ func (p *peer) handlePing(msg *p2p.Ping) { p.Send(p.onClosingCtx, pongMessage) } -func (p *peer) getUptimes() (uint32, []*p2p.SubnetUptime) { +func (p *peer) getUptime() uint32 { primaryUptime, err := p.UptimeCalculator.CalculateUptimePercent( p.id, - constants.PrimaryNetworkID, ) if err != nil { p.Log.Debug(failedToGetUptimeLog, @@ -850,45 +791,12 @@ func (p *peer) getUptimes() (uint32, []*p2p.SubnetUptime) { primaryUptime = 0 } - subnetUptimes := make([]*p2p.SubnetUptime, 0, p.MySubnets.Len()) - for subnetID := range p.MySubnets { - if !p.trackedSubnets.Contains(subnetID) { - continue - } - - subnetUptime, err := p.UptimeCalculator.CalculateUptimePercent(p.id, subnetID) - if err != nil { - p.Log.Debug(failedToGetUptimeLog, - zap.Stringer("nodeID", p.id), - zap.Stringer("subnetID", subnetID), - zap.Error(err), - ) - continue - } - - subnetID := subnetID - subnetUptimes = append(subnetUptimes, &p2p.SubnetUptime{ - SubnetId: subnetID[:], - Uptime: uint32(subnetUptime * 100), - }) - } - primaryUptimePercent := uint32(primaryUptime * 100) - return primaryUptimePercent, subnetUptimes + return primaryUptimePercent } func (*peer) handlePong(*p2p.Pong) {} -// Record that the given peer perceives our uptime for the given [subnetID] -// to be [uptime]. -// Assumes [uptime] is in the range [0, 100] and [subnetID] is a valid ID of a -// subnet this peer tracks. -func (p *peer) observeUptime(subnetID ids.ID, uptime uint32) { - p.observedUptimesLock.Lock() - p.observedUptimes[subnetID] = uptime // [0, 100] percentage - p.observedUptimesLock.Unlock() -} - func (p *peer) handleHandshake(msg *p2p.Handshake) { if p.gotHandshake.Get() { p.Log.Debug(malformedMessageLog, diff --git a/network/peer/peer_test.go b/network/peer/peer_test.go index e29edbe17ba6..ae2f86a74fc1 100644 --- a/network/peer/peer_test.go +++ b/network/peer/peer_test.go @@ -17,7 +17,6 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/message" "github.com/ava-labs/avalanchego/network/throttling" - "github.com/ava-labs/avalanchego/proto/pb/p2p" "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/snow/networking/tracker" "github.com/ava-labs/avalanchego/snow/uptime" @@ -211,122 +210,35 @@ func TestSend(t *testing.T) { } func TestPingUptimes(t *testing.T) { - trackedSubnetID := ids.GenerateTestID() - untrackedSubnetID := ids.GenerateTestID() - sharedConfig := newConfig(t) - sharedConfig.MySubnets = set.Of(trackedSubnetID) - - testCases := []struct { - name string - msg message.OutboundMessage - shouldClose bool - assertFn func(*require.Assertions, *testPeer) - }{ - { - name: "primary network only", - msg: func() message.OutboundMessage { - pingMsg, err := sharedConfig.MessageCreator.Ping(1, nil) - require.NoError(t, err) - return pingMsg - }(), - shouldClose: false, - assertFn: func(require *require.Assertions, peer *testPeer) { - uptime, ok := peer.ObservedUptime(constants.PrimaryNetworkID) - require.True(ok) - require.Equal(uint32(1), uptime) - - uptime, ok = peer.ObservedUptime(trackedSubnetID) - require.False(ok) - require.Zero(uptime) - }, - }, - { - name: "primary network and subnet", - msg: func() message.OutboundMessage { - pingMsg, err := sharedConfig.MessageCreator.Ping( - 1, - []*p2p.SubnetUptime{ - { - SubnetId: trackedSubnetID[:], - Uptime: 1, - }, - }, - ) - require.NoError(t, err) - return pingMsg - }(), - shouldClose: false, - assertFn: func(require *require.Assertions, peer *testPeer) { - uptime, ok := peer.ObservedUptime(constants.PrimaryNetworkID) - require.True(ok) - require.Equal(uint32(1), uptime) - - uptime, ok = peer.ObservedUptime(trackedSubnetID) - require.True(ok) - require.Equal(uint32(1), uptime) - }, - }, - { - name: "primary network and non tracked subnet", - msg: func() message.OutboundMessage { - pingMsg, err := sharedConfig.MessageCreator.Ping( - 1, - []*p2p.SubnetUptime{ - { - // Providing the untrackedSubnetID here should cause - // the remote peer to disconnect from us. - SubnetId: untrackedSubnetID[:], - Uptime: 1, - }, - { - SubnetId: trackedSubnetID[:], - Uptime: 1, - }, - }, - ) - require.NoError(t, err) - return pingMsg - }(), - shouldClose: true, - assertFn: nil, - }, - } // The raw peers are generated outside of the test cases to avoid generating // many TLS keys. rawPeer0 := newRawTestPeer(t, sharedConfig) rawPeer1 := newRawTestPeer(t, sharedConfig) - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - require := require.New(t) - - peer0, peer1 := startTestPeers(rawPeer0, rawPeer1) - awaitReady(t, peer0, peer1) - defer func() { - peer1.StartClose() - peer0.StartClose() - require.NoError(peer0.AwaitClosed(context.Background())) - require.NoError(peer1.AwaitClosed(context.Background())) - }() - - require.True(peer0.Send(context.Background(), tc.msg)) + require := require.New(t) - if tc.shouldClose { - require.NoError(peer1.AwaitClosed(context.Background())) - return - } + peer0, peer1 := startTestPeers(rawPeer0, rawPeer1) + awaitReady(t, peer0, peer1) + defer func() { + peer1.StartClose() + peer0.StartClose() + require.NoError(peer0.AwaitClosed(context.Background())) + require.NoError(peer1.AwaitClosed(context.Background())) + }() + pingMsg, err := sharedConfig.MessageCreator.Ping(0) + require.NoError(err) + require.True(peer0.Send(context.Background(), pingMsg)) - // we send Get message after ping to ensure Ping is handled by the - // time Get is handled. This is because Get is routed to the handler - // whereas Ping is handled by the peer directly. We have no way to - // know when the peer has handled the Ping message. - sendAndFlush(t, peer0, peer1) + // we send Get message after ping to ensure Ping is handled by the + // time Get is handled. This is because Get is routed to the handler + // whereas Ping is handled by the peer directly. We have no way to + // know when the peer has handled the Ping message. + sendAndFlush(t, peer0, peer1) - tc.assertFn(require, peer1) - }) - } + uptime := peer1.ObservedUptime() + require.Equal(uint32(0), uptime) } func TestTrackedSubnets(t *testing.T) { diff --git a/network/peer/set_test.go b/network/peer/set_test.go index c28c1ce7ea98..fb84b5e67d74 100644 --- a/network/peer/set_test.go +++ b/network/peer/set_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils" ) func TestSet(t *testing.T) { @@ -18,12 +18,12 @@ func TestSet(t *testing.T) { set := NewSet() peer1 := &peer{ - id: ids.BuildTestNodeID([]byte{0x01}), - observedUptimes: map[ids.ID]uint32{constants.PrimaryNetworkID: 0}, + id: ids.BuildTestNodeID([]byte{0x01}), + observedUptime: *utils.NewAtomic[uint32](0), } updatedPeer1 := &peer{ - id: ids.BuildTestNodeID([]byte{0x01}), - observedUptimes: map[ids.ID]uint32{constants.PrimaryNetworkID: 1}, + id: ids.BuildTestNodeID([]byte{0x01}), + observedUptime: *utils.NewAtomic[uint32](1), } peer2 := &peer{ id: ids.BuildTestNodeID([]byte{0x02}), @@ -42,8 +42,8 @@ func TestSet(t *testing.T) { set.Add(peer1) retrievedPeer1, peer1Found := set.GetByID(peer1.id) require.True(peer1Found) - observed1, _ := peer1.ObservedUptime(constants.PrimaryNetworkID) - observed2, _ := retrievedPeer1.ObservedUptime(constants.PrimaryNetworkID) + observed1 := peer1.ObservedUptime() + observed2 := retrievedPeer1.ObservedUptime() require.Equal(observed1, observed2) require.Equal(1, set.Len()) @@ -51,8 +51,8 @@ func TestSet(t *testing.T) { set.Add(updatedPeer1) retrievedPeer1, peer1Found = set.GetByID(peer1.id) require.True(peer1Found) - observed1, _ = updatedPeer1.ObservedUptime(constants.PrimaryNetworkID) - observed2, _ = retrievedPeer1.ObservedUptime(constants.PrimaryNetworkID) + observed1 = updatedPeer1.ObservedUptime() + observed2 = retrievedPeer1.ObservedUptime() require.Equal(observed1, observed2) require.Equal(1, set.Len()) @@ -60,8 +60,8 @@ func TestSet(t *testing.T) { set.Add(peer2) retrievedPeer2, peer2Found := set.GetByID(peer2.id) require.True(peer2Found) - observed1, _ = peer2.ObservedUptime(constants.PrimaryNetworkID) - observed2, _ = retrievedPeer2.ObservedUptime(constants.PrimaryNetworkID) + observed1 = peer2.ObservedUptime() + observed2 = retrievedPeer2.ObservedUptime() require.Equal(observed1, observed2) require.Equal(2, set.Len()) diff --git a/proto/p2p/p2p.proto b/proto/p2p/p2p.proto index 53c0c84de303..5d0a3a31a1eb 100644 --- a/proto/p2p/p2p.proto +++ b/proto/p2p/p2p.proto @@ -64,16 +64,7 @@ message Message { message Ping { // Uptime percentage on the primary network [0, 100] uint32 uptime = 1; - // Uptime percentage on subnets - repeated SubnetUptime subnet_uptimes = 2; -} - -// SubnetUptime is a descriptor for a peer's perceived uptime on a subnet. -message SubnetUptime { - // Subnet the peer is validating - bytes subnet_id = 1; - // Uptime percentage on the subnet [0, 100] - uint32 uptime = 2; + reserved 2; // Until E upgrade is activated. } // Pong is sent in response to a Ping. diff --git a/proto/pb/p2p/p2p.pb.go b/proto/pb/p2p/p2p.pb.go index bd55ad703546..ecb33f05a920 100644 --- a/proto/pb/p2p/p2p.pb.go +++ b/proto/pb/p2p/p2p.pb.go @@ -498,8 +498,6 @@ type Ping struct { // Uptime percentage on the primary network [0, 100] Uptime uint32 `protobuf:"varint,1,opt,name=uptime,proto3" json:"uptime,omitempty"` - // Uptime percentage on subnets - SubnetUptimes []*SubnetUptime `protobuf:"bytes,2,rep,name=subnet_uptimes,json=subnetUptimes,proto3" json:"subnet_uptimes,omitempty"` } func (x *Ping) Reset() { @@ -541,71 +539,6 @@ func (x *Ping) GetUptime() uint32 { return 0 } -func (x *Ping) GetSubnetUptimes() []*SubnetUptime { - if x != nil { - return x.SubnetUptimes - } - return nil -} - -// SubnetUptime is a descriptor for a peer's perceived uptime on a subnet. -type SubnetUptime struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Subnet the peer is validating - SubnetId []byte `protobuf:"bytes,1,opt,name=subnet_id,json=subnetId,proto3" json:"subnet_id,omitempty"` - // Uptime percentage on the subnet [0, 100] - Uptime uint32 `protobuf:"varint,2,opt,name=uptime,proto3" json:"uptime,omitempty"` -} - -func (x *SubnetUptime) Reset() { - *x = SubnetUptime{} - if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SubnetUptime) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubnetUptime) ProtoMessage() {} - -func (x *SubnetUptime) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubnetUptime.ProtoReflect.Descriptor instead. -func (*SubnetUptime) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{2} -} - -func (x *SubnetUptime) GetSubnetId() []byte { - if x != nil { - return x.SubnetId - } - return nil -} - -func (x *SubnetUptime) GetUptime() uint32 { - if x != nil { - return x.Uptime - } - return 0 -} - // Pong is sent in response to a Ping. type Pong struct { state protoimpl.MessageState @@ -616,7 +549,7 @@ type Pong struct { func (x *Pong) Reset() { *x = Pong{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[3] + mi := &file_p2p_p2p_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -629,7 +562,7 @@ func (x *Pong) String() string { func (*Pong) ProtoMessage() {} func (x *Pong) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[3] + mi := &file_p2p_p2p_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -642,7 +575,7 @@ func (x *Pong) ProtoReflect() protoreflect.Message { // Deprecated: Use Pong.ProtoReflect.Descriptor instead. func (*Pong) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{3} + return file_p2p_p2p_proto_rawDescGZIP(), []int{2} } // Handshake is the first outbound message sent to a peer when a connection is @@ -684,7 +617,7 @@ type Handshake struct { func (x *Handshake) Reset() { *x = Handshake{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[4] + mi := &file_p2p_p2p_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -697,7 +630,7 @@ func (x *Handshake) String() string { func (*Handshake) ProtoMessage() {} func (x *Handshake) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[4] + mi := &file_p2p_p2p_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -710,7 +643,7 @@ func (x *Handshake) ProtoReflect() protoreflect.Message { // Deprecated: Use Handshake.ProtoReflect.Descriptor instead. func (*Handshake) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{4} + return file_p2p_p2p_proto_rawDescGZIP(), []int{3} } func (x *Handshake) GetNetworkId() uint32 { @@ -814,7 +747,7 @@ type Client struct { func (x *Client) Reset() { *x = Client{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[5] + mi := &file_p2p_p2p_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -827,7 +760,7 @@ func (x *Client) String() string { func (*Client) ProtoMessage() {} func (x *Client) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[5] + mi := &file_p2p_p2p_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -840,7 +773,7 @@ func (x *Client) ProtoReflect() protoreflect.Message { // Deprecated: Use Client.ProtoReflect.Descriptor instead. func (*Client) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{5} + return file_p2p_p2p_proto_rawDescGZIP(), []int{4} } func (x *Client) GetName() string { @@ -884,7 +817,7 @@ type BloomFilter struct { func (x *BloomFilter) Reset() { *x = BloomFilter{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[6] + mi := &file_p2p_p2p_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -897,7 +830,7 @@ func (x *BloomFilter) String() string { func (*BloomFilter) ProtoMessage() {} func (x *BloomFilter) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[6] + mi := &file_p2p_p2p_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -910,7 +843,7 @@ func (x *BloomFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use BloomFilter.ProtoReflect.Descriptor instead. func (*BloomFilter) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{6} + return file_p2p_p2p_proto_rawDescGZIP(), []int{5} } func (x *BloomFilter) GetFilter() []byte { @@ -950,7 +883,7 @@ type ClaimedIpPort struct { func (x *ClaimedIpPort) Reset() { *x = ClaimedIpPort{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[7] + mi := &file_p2p_p2p_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -963,7 +896,7 @@ func (x *ClaimedIpPort) String() string { func (*ClaimedIpPort) ProtoMessage() {} func (x *ClaimedIpPort) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[7] + mi := &file_p2p_p2p_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -976,7 +909,7 @@ func (x *ClaimedIpPort) ProtoReflect() protoreflect.Message { // Deprecated: Use ClaimedIpPort.ProtoReflect.Descriptor instead. func (*ClaimedIpPort) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{7} + return file_p2p_p2p_proto_rawDescGZIP(), []int{6} } func (x *ClaimedIpPort) GetX509Certificate() []byte { @@ -1038,7 +971,7 @@ type GetPeerList struct { func (x *GetPeerList) Reset() { *x = GetPeerList{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[8] + mi := &file_p2p_p2p_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1051,7 +984,7 @@ func (x *GetPeerList) String() string { func (*GetPeerList) ProtoMessage() {} func (x *GetPeerList) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[8] + mi := &file_p2p_p2p_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1064,7 +997,7 @@ func (x *GetPeerList) ProtoReflect() protoreflect.Message { // Deprecated: Use GetPeerList.ProtoReflect.Descriptor instead. func (*GetPeerList) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{8} + return file_p2p_p2p_proto_rawDescGZIP(), []int{7} } func (x *GetPeerList) GetKnownPeers() *BloomFilter { @@ -1094,7 +1027,7 @@ type PeerList struct { func (x *PeerList) Reset() { *x = PeerList{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[9] + mi := &file_p2p_p2p_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1107,7 +1040,7 @@ func (x *PeerList) String() string { func (*PeerList) ProtoMessage() {} func (x *PeerList) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[9] + mi := &file_p2p_p2p_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1120,7 +1053,7 @@ func (x *PeerList) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerList.ProtoReflect.Descriptor instead. func (*PeerList) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{9} + return file_p2p_p2p_proto_rawDescGZIP(), []int{8} } func (x *PeerList) GetClaimedIpPorts() []*ClaimedIpPort { @@ -1148,7 +1081,7 @@ type GetStateSummaryFrontier struct { func (x *GetStateSummaryFrontier) Reset() { *x = GetStateSummaryFrontier{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[10] + mi := &file_p2p_p2p_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1161,7 +1094,7 @@ func (x *GetStateSummaryFrontier) String() string { func (*GetStateSummaryFrontier) ProtoMessage() {} func (x *GetStateSummaryFrontier) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[10] + mi := &file_p2p_p2p_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1174,7 +1107,7 @@ func (x *GetStateSummaryFrontier) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStateSummaryFrontier.ProtoReflect.Descriptor instead. func (*GetStateSummaryFrontier) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{10} + return file_p2p_p2p_proto_rawDescGZIP(), []int{9} } func (x *GetStateSummaryFrontier) GetChainId() []byte { @@ -1215,7 +1148,7 @@ type StateSummaryFrontier struct { func (x *StateSummaryFrontier) Reset() { *x = StateSummaryFrontier{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[11] + mi := &file_p2p_p2p_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1228,7 +1161,7 @@ func (x *StateSummaryFrontier) String() string { func (*StateSummaryFrontier) ProtoMessage() {} func (x *StateSummaryFrontier) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[11] + mi := &file_p2p_p2p_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1241,7 +1174,7 @@ func (x *StateSummaryFrontier) ProtoReflect() protoreflect.Message { // Deprecated: Use StateSummaryFrontier.ProtoReflect.Descriptor instead. func (*StateSummaryFrontier) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{11} + return file_p2p_p2p_proto_rawDescGZIP(), []int{10} } func (x *StateSummaryFrontier) GetChainId() []byte { @@ -1285,7 +1218,7 @@ type GetAcceptedStateSummary struct { func (x *GetAcceptedStateSummary) Reset() { *x = GetAcceptedStateSummary{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[12] + mi := &file_p2p_p2p_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1298,7 +1231,7 @@ func (x *GetAcceptedStateSummary) String() string { func (*GetAcceptedStateSummary) ProtoMessage() {} func (x *GetAcceptedStateSummary) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[12] + mi := &file_p2p_p2p_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1311,7 +1244,7 @@ func (x *GetAcceptedStateSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAcceptedStateSummary.ProtoReflect.Descriptor instead. func (*GetAcceptedStateSummary) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{12} + return file_p2p_p2p_proto_rawDescGZIP(), []int{11} } func (x *GetAcceptedStateSummary) GetChainId() []byte { @@ -1359,7 +1292,7 @@ type AcceptedStateSummary struct { func (x *AcceptedStateSummary) Reset() { *x = AcceptedStateSummary{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[13] + mi := &file_p2p_p2p_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1372,7 +1305,7 @@ func (x *AcceptedStateSummary) String() string { func (*AcceptedStateSummary) ProtoMessage() {} func (x *AcceptedStateSummary) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[13] + mi := &file_p2p_p2p_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1385,7 +1318,7 @@ func (x *AcceptedStateSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use AcceptedStateSummary.ProtoReflect.Descriptor instead. func (*AcceptedStateSummary) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{13} + return file_p2p_p2p_proto_rawDescGZIP(), []int{12} } func (x *AcceptedStateSummary) GetChainId() []byte { @@ -1428,7 +1361,7 @@ type GetAcceptedFrontier struct { func (x *GetAcceptedFrontier) Reset() { *x = GetAcceptedFrontier{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[14] + mi := &file_p2p_p2p_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1441,7 +1374,7 @@ func (x *GetAcceptedFrontier) String() string { func (*GetAcceptedFrontier) ProtoMessage() {} func (x *GetAcceptedFrontier) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[14] + mi := &file_p2p_p2p_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1454,7 +1387,7 @@ func (x *GetAcceptedFrontier) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAcceptedFrontier.ProtoReflect.Descriptor instead. func (*GetAcceptedFrontier) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{14} + return file_p2p_p2p_proto_rawDescGZIP(), []int{13} } func (x *GetAcceptedFrontier) GetChainId() []byte { @@ -1497,7 +1430,7 @@ type AcceptedFrontier struct { func (x *AcceptedFrontier) Reset() { *x = AcceptedFrontier{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[15] + mi := &file_p2p_p2p_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1510,7 +1443,7 @@ func (x *AcceptedFrontier) String() string { func (*AcceptedFrontier) ProtoMessage() {} func (x *AcceptedFrontier) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[15] + mi := &file_p2p_p2p_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1523,7 +1456,7 @@ func (x *AcceptedFrontier) ProtoReflect() protoreflect.Message { // Deprecated: Use AcceptedFrontier.ProtoReflect.Descriptor instead. func (*AcceptedFrontier) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{15} + return file_p2p_p2p_proto_rawDescGZIP(), []int{14} } func (x *AcceptedFrontier) GetChainId() []byte { @@ -1569,7 +1502,7 @@ type GetAccepted struct { func (x *GetAccepted) Reset() { *x = GetAccepted{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[16] + mi := &file_p2p_p2p_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1582,7 +1515,7 @@ func (x *GetAccepted) String() string { func (*GetAccepted) ProtoMessage() {} func (x *GetAccepted) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[16] + mi := &file_p2p_p2p_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1595,7 +1528,7 @@ func (x *GetAccepted) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAccepted.ProtoReflect.Descriptor instead. func (*GetAccepted) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{16} + return file_p2p_p2p_proto_rawDescGZIP(), []int{15} } func (x *GetAccepted) GetChainId() []byte { @@ -1646,7 +1579,7 @@ type Accepted struct { func (x *Accepted) Reset() { *x = Accepted{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[17] + mi := &file_p2p_p2p_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1659,7 +1592,7 @@ func (x *Accepted) String() string { func (*Accepted) ProtoMessage() {} func (x *Accepted) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[17] + mi := &file_p2p_p2p_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1672,7 +1605,7 @@ func (x *Accepted) ProtoReflect() protoreflect.Message { // Deprecated: Use Accepted.ProtoReflect.Descriptor instead. func (*Accepted) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{17} + return file_p2p_p2p_proto_rawDescGZIP(), []int{16} } func (x *Accepted) GetChainId() []byte { @@ -1719,7 +1652,7 @@ type GetAncestors struct { func (x *GetAncestors) Reset() { *x = GetAncestors{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[18] + mi := &file_p2p_p2p_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1732,7 +1665,7 @@ func (x *GetAncestors) String() string { func (*GetAncestors) ProtoMessage() {} func (x *GetAncestors) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[18] + mi := &file_p2p_p2p_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1745,7 +1678,7 @@ func (x *GetAncestors) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAncestors.ProtoReflect.Descriptor instead. func (*GetAncestors) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{18} + return file_p2p_p2p_proto_rawDescGZIP(), []int{17} } func (x *GetAncestors) GetChainId() []byte { @@ -1803,7 +1736,7 @@ type Ancestors struct { func (x *Ancestors) Reset() { *x = Ancestors{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[19] + mi := &file_p2p_p2p_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1816,7 +1749,7 @@ func (x *Ancestors) String() string { func (*Ancestors) ProtoMessage() {} func (x *Ancestors) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[19] + mi := &file_p2p_p2p_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1829,7 +1762,7 @@ func (x *Ancestors) ProtoReflect() protoreflect.Message { // Deprecated: Use Ancestors.ProtoReflect.Descriptor instead. func (*Ancestors) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{19} + return file_p2p_p2p_proto_rawDescGZIP(), []int{18} } func (x *Ancestors) GetChainId() []byte { @@ -1874,7 +1807,7 @@ type Get struct { func (x *Get) Reset() { *x = Get{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[20] + mi := &file_p2p_p2p_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1887,7 +1820,7 @@ func (x *Get) String() string { func (*Get) ProtoMessage() {} func (x *Get) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[20] + mi := &file_p2p_p2p_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1900,7 +1833,7 @@ func (x *Get) ProtoReflect() protoreflect.Message { // Deprecated: Use Get.ProtoReflect.Descriptor instead. func (*Get) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{20} + return file_p2p_p2p_proto_rawDescGZIP(), []int{19} } func (x *Get) GetChainId() []byte { @@ -1948,7 +1881,7 @@ type Put struct { func (x *Put) Reset() { *x = Put{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[21] + mi := &file_p2p_p2p_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1961,7 +1894,7 @@ func (x *Put) String() string { func (*Put) ProtoMessage() {} func (x *Put) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[21] + mi := &file_p2p_p2p_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1974,7 +1907,7 @@ func (x *Put) ProtoReflect() protoreflect.Message { // Deprecated: Use Put.ProtoReflect.Descriptor instead. func (*Put) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{21} + return file_p2p_p2p_proto_rawDescGZIP(), []int{20} } func (x *Put) GetChainId() []byte { @@ -2021,7 +1954,7 @@ type PushQuery struct { func (x *PushQuery) Reset() { *x = PushQuery{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[22] + mi := &file_p2p_p2p_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2034,7 +1967,7 @@ func (x *PushQuery) String() string { func (*PushQuery) ProtoMessage() {} func (x *PushQuery) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[22] + mi := &file_p2p_p2p_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2047,7 +1980,7 @@ func (x *PushQuery) ProtoReflect() protoreflect.Message { // Deprecated: Use PushQuery.ProtoReflect.Descriptor instead. func (*PushQuery) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{22} + return file_p2p_p2p_proto_rawDescGZIP(), []int{21} } func (x *PushQuery) GetChainId() []byte { @@ -2108,7 +2041,7 @@ type PullQuery struct { func (x *PullQuery) Reset() { *x = PullQuery{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[23] + mi := &file_p2p_p2p_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2121,7 +2054,7 @@ func (x *PullQuery) String() string { func (*PullQuery) ProtoMessage() {} func (x *PullQuery) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[23] + mi := &file_p2p_p2p_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2134,7 +2067,7 @@ func (x *PullQuery) ProtoReflect() protoreflect.Message { // Deprecated: Use PullQuery.ProtoReflect.Descriptor instead. func (*PullQuery) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{23} + return file_p2p_p2p_proto_rawDescGZIP(), []int{22} } func (x *PullQuery) GetChainId() []byte { @@ -2194,7 +2127,7 @@ type Chits struct { func (x *Chits) Reset() { *x = Chits{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[24] + mi := &file_p2p_p2p_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2207,7 +2140,7 @@ func (x *Chits) String() string { func (*Chits) ProtoMessage() {} func (x *Chits) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[24] + mi := &file_p2p_p2p_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2220,7 +2153,7 @@ func (x *Chits) ProtoReflect() protoreflect.Message { // Deprecated: Use Chits.ProtoReflect.Descriptor instead. func (*Chits) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{24} + return file_p2p_p2p_proto_rawDescGZIP(), []int{23} } func (x *Chits) GetChainId() []byte { @@ -2280,7 +2213,7 @@ type AppRequest struct { func (x *AppRequest) Reset() { *x = AppRequest{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[25] + mi := &file_p2p_p2p_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2293,7 +2226,7 @@ func (x *AppRequest) String() string { func (*AppRequest) ProtoMessage() {} func (x *AppRequest) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[25] + mi := &file_p2p_p2p_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2306,7 +2239,7 @@ func (x *AppRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AppRequest.ProtoReflect.Descriptor instead. func (*AppRequest) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{25} + return file_p2p_p2p_proto_rawDescGZIP(), []int{24} } func (x *AppRequest) GetChainId() []byte { @@ -2354,7 +2287,7 @@ type AppResponse struct { func (x *AppResponse) Reset() { *x = AppResponse{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[26] + mi := &file_p2p_p2p_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2367,7 +2300,7 @@ func (x *AppResponse) String() string { func (*AppResponse) ProtoMessage() {} func (x *AppResponse) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[26] + mi := &file_p2p_p2p_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2380,7 +2313,7 @@ func (x *AppResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AppResponse.ProtoReflect.Descriptor instead. func (*AppResponse) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{26} + return file_p2p_p2p_proto_rawDescGZIP(), []int{25} } func (x *AppResponse) GetChainId() []byte { @@ -2423,7 +2356,7 @@ type AppError struct { func (x *AppError) Reset() { *x = AppError{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[27] + mi := &file_p2p_p2p_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2436,7 +2369,7 @@ func (x *AppError) String() string { func (*AppError) ProtoMessage() {} func (x *AppError) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[27] + mi := &file_p2p_p2p_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2449,7 +2382,7 @@ func (x *AppError) ProtoReflect() protoreflect.Message { // Deprecated: Use AppError.ProtoReflect.Descriptor instead. func (*AppError) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{27} + return file_p2p_p2p_proto_rawDescGZIP(), []int{26} } func (x *AppError) GetChainId() []byte { @@ -2495,7 +2428,7 @@ type AppGossip struct { func (x *AppGossip) Reset() { *x = AppGossip{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[28] + mi := &file_p2p_p2p_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2508,7 +2441,7 @@ func (x *AppGossip) String() string { func (*AppGossip) ProtoMessage() {} func (x *AppGossip) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[28] + mi := &file_p2p_p2p_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2521,7 +2454,7 @@ func (x *AppGossip) ProtoReflect() protoreflect.Message { // Deprecated: Use AppGossip.ProtoReflect.Descriptor instead. func (*AppGossip) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{28} + return file_p2p_p2p_proto_rawDescGZIP(), []int{27} } func (x *AppGossip) GetChainId() []byte { @@ -2629,240 +2562,232 @@ var file_p2p_p2p_proto_rawDesc = []byte{ 0x6f, 0x72, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x41, 0x70, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x08, 0x61, 0x70, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4a, 0x04, - 0x08, 0x01, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x24, 0x10, 0x25, 0x22, 0x58, 0x0a, 0x04, 0x50, 0x69, + 0x08, 0x01, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x24, 0x10, 0x25, 0x22, 0x24, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x0e, 0x73, 0x75, - 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, - 0x70, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x22, 0x43, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, - 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x6e, - 0x67, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xb3, 0x03, - 0x0a, 0x09, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x09, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x79, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6d, 0x79, 0x54, - 0x69, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x12, 0x17, 0x0a, 0x07, - 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x69, - 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x70, 0x5f, 0x73, 0x69, 0x67, 0x6e, - 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, - 0x69, 0x70, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x23, 0x0a, - 0x0e, 0x69, 0x70, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x73, 0x69, 0x67, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x69, 0x70, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x53, - 0x69, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x73, 0x75, - 0x62, 0x6e, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e, 0x74, 0x72, 0x61, - 0x63, 0x6b, 0x65, 0x64, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x06, 0x63, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x32, - 0x70, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x63, - 0x70, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x41, 0x63, 0x70, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x70, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x41, 0x63, 0x70, 0x73, 0x12, 0x31, 0x0a, 0x0b, - 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x52, 0x0a, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, - 0x1c, 0x0a, 0x0a, 0x69, 0x70, 0x5f, 0x62, 0x6c, 0x73, 0x5f, 0x73, 0x69, 0x67, 0x18, 0x0d, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x08, 0x69, 0x70, 0x42, 0x6c, 0x73, 0x53, 0x69, 0x67, 0x4a, 0x04, 0x08, - 0x05, 0x10, 0x06, 0x22, 0x5e, 0x0a, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x14, 0x0a, - 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x70, 0x61, - 0x74, 0x63, 0x68, 0x22, 0x39, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, - 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, - 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x22, 0xbd, - 0x01, 0x0a, 0x0d, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, 0x6f, 0x72, 0x74, - 0x12, 0x29, 0x0a, 0x10, 0x78, 0x35, 0x30, 0x39, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x78, 0x35, 0x30, 0x39, - 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, - 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x69, 0x70, - 0x41, 0x64, 0x64, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x69, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1c, 0x0a, - 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1c, 0x0a, 0x09, 0x73, - 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, - 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, - 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x22, 0x40, - 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x50, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x31, 0x0a, - 0x0b, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x65, 0x65, 0x72, 0x73, - 0x22, 0x48, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x10, - 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x5f, 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x43, 0x6c, 0x61, - 0x69, 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x0e, 0x63, 0x6c, 0x61, 0x69, - 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x73, 0x22, 0x6f, 0x0a, 0x17, 0x47, 0x65, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, - 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x6a, 0x0a, 0x14, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, - 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, - 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x89, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x41, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, - 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x73, 0x22, 0x71, 0x0a, 0x14, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x49, 0x64, 0x73, 0x22, 0x71, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, - 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, - 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, - 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, - 0x69, 0x6e, 0x65, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0x6f, 0x0a, 0x10, 0x41, 0x63, 0x63, - 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, - 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x22, 0x8e, 0x01, 0x0a, 0x0b, 0x47, - 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, + 0x28, 0x0d, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, + 0x22, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x6e, 0x67, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x4a, 0x04, + 0x08, 0x02, 0x10, 0x03, 0x22, 0xb3, 0x03, 0x0a, 0x09, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, + 0x6b, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, + 0x64, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x06, 0x6d, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x69, 0x70, 0x41, + 0x64, 0x64, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x69, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x26, 0x0a, 0x0f, + 0x69, 0x70, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x69, 0x70, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0e, 0x69, 0x70, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, + 0x69, 0x64, 0x5f, 0x73, 0x69, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x69, 0x70, + 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x53, 0x69, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, + 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, + 0x28, 0x0c, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x75, 0x62, 0x6e, 0x65, + 0x74, 0x73, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, + 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x75, 0x70, 0x70, 0x6f, + 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x70, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0d, 0x52, + 0x0d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x41, 0x63, 0x70, 0x73, 0x12, 0x23, + 0x0a, 0x0d, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x70, 0x73, 0x18, + 0x0b, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x41, + 0x63, 0x70, 0x73, 0x12, 0x31, 0x0a, 0x0b, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x70, 0x65, 0x65, + 0x72, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x42, + 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x6b, 0x6e, 0x6f, 0x77, + 0x6e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x1c, 0x0a, 0x0a, 0x69, 0x70, 0x5f, 0x62, 0x6c, 0x73, + 0x5f, 0x73, 0x69, 0x67, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x69, 0x70, 0x42, 0x6c, + 0x73, 0x53, 0x69, 0x67, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0x5e, 0x0a, 0x06, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x6a, 0x6f, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x14, + 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6d, + 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x22, 0x39, 0x0a, 0x0b, 0x42, 0x6c, + 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x04, 0x73, 0x61, 0x6c, 0x74, 0x22, 0xbd, 0x01, 0x0a, 0x0d, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x65, + 0x64, 0x49, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x78, 0x35, 0x30, 0x39, 0x5f, + 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0f, 0x78, 0x35, 0x30, 0x39, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x06, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x69, + 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x69, 0x70, + 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x04, 0x74, 0x78, 0x49, 0x64, 0x22, 0x40, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x50, 0x65, 0x65, 0x72, + 0x4c, 0x69, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x0b, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x70, 0x65, + 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x32, 0x70, 0x2e, + 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x6b, 0x6e, 0x6f, + 0x77, 0x6e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x22, 0x48, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x5f, 0x69, + 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x70, 0x32, 0x70, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, 0x6f, 0x72, + 0x74, 0x52, 0x0e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, 0x6f, 0x72, 0x74, + 0x73, 0x22, 0x6f, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, + 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, + 0x6e, 0x65, 0x22, 0x6a, 0x0a, 0x14, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x89, + 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, - 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x49, 0x64, 0x73, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0x69, 0x0a, 0x08, 0x41, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, - 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0xb9, 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x41, 0x6e, - 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, - 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, - 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, - 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, - 0x70, 0x65, 0x22, 0x65, 0x0a, 0x09, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, - 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x22, 0x84, 0x01, 0x0a, 0x03, 0x47, 0x65, - 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x04, 0x52, 0x07, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0x22, 0x71, 0x0a, 0x14, 0x41, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0c, 0x52, 0x0a, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x49, 0x64, 0x73, 0x22, 0x71, 0x0a, + 0x13, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, + 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, + 0x22, 0x6f, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, + 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, + 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, + 0x64, 0x22, 0x8e, 0x01, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, - 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, - 0x22, 0x5d, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, - 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x22, - 0xb0, 0x01, 0x0a, 0x09, 0x50, 0x75, 0x73, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x19, 0x0a, - 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, - 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, - 0x69, 0x6e, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x4a, 0x04, 0x08, 0x05, - 0x10, 0x06, 0x22, 0xb5, 0x01, 0x0a, 0x09, 0x50, 0x75, 0x6c, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, - 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, - 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x48, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0xba, 0x01, 0x0a, 0x05, 0x43, - 0x68, 0x69, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, + 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x73, 0x4a, 0x04, 0x08, 0x05, + 0x10, 0x06, 0x22, 0x69, 0x0a, 0x08, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x19, + 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, + 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0xb9, 0x01, + 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x19, + 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, + 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, + 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, + 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, + 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, + 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x65, 0x0a, 0x09, 0x41, 0x6e, 0x63, + 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, + 0x22, 0x84, 0x01, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, + 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, + 0x64, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0x5d, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x19, + 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x22, 0xb0, 0x01, 0x0a, 0x09, 0x50, 0x75, 0x73, 0x68, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, - 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x49, - 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, - 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, - 0x69, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x49, 0x64, 0x41, - 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x7f, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, - 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, - 0x70, 0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, - 0x61, 0x70, 0x70, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x64, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0xb5, 0x01, 0x0a, 0x09, 0x50, 0x75, + 0x6c, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, - 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x88, - 0x01, 0x0a, 0x08, 0x41, 0x70, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, + 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x65, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x4a, 0x04, 0x08, 0x05, 0x10, + 0x06, 0x22, 0xba, 0x01, 0x0a, 0x05, 0x43, 0x68, 0x69, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x11, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x43, 0x0a, 0x09, 0x41, 0x70, 0x70, - 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, - 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, 0x74, 0x65, 0x73, 0x2a, 0x5d, - 0x0a, 0x0a, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, - 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, - 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x45, 0x4e, 0x47, - 0x49, 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x56, 0x41, 0x4c, 0x41, 0x4e, 0x43, - 0x48, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x53, 0x4e, 0x4f, 0x57, 0x4d, 0x41, 0x4e, 0x10, 0x02, 0x42, 0x2e, 0x5a, - 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x76, 0x61, 0x2d, - 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x61, 0x76, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x67, 0x6f, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x2f, 0x70, 0x32, 0x70, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, + 0x65, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, + 0x70, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, + 0x72, 0x72, 0x65, 0x64, 0x49, 0x64, 0x41, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x7f, + 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, + 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, + 0x6e, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, + 0x64, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, + 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x70, 0x70, + 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x88, 0x01, 0x0a, 0x08, 0x41, 0x70, 0x70, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x11, + 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x22, 0x43, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x12, 0x19, 0x0a, + 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x70, 0x70, + 0x42, 0x79, 0x74, 0x65, 0x73, 0x2a, 0x5d, 0x0a, 0x0a, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x19, 0x0a, 0x15, 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x41, 0x56, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x48, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x45, + 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x4e, 0x4f, 0x57, 0x4d, + 0x41, 0x4e, 0x10, 0x02, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x61, 0x76, 0x61, 0x2d, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x61, 0x76, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x68, 0x65, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, + 0x2f, 0x70, 0x32, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2878,75 +2803,73 @@ func file_p2p_p2p_proto_rawDescGZIP() []byte { } var file_p2p_p2p_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_p2p_p2p_proto_msgTypes = make([]protoimpl.MessageInfo, 29) +var file_p2p_p2p_proto_msgTypes = make([]protoimpl.MessageInfo, 28) var file_p2p_p2p_proto_goTypes = []interface{}{ (EngineType)(0), // 0: p2p.EngineType (*Message)(nil), // 1: p2p.Message (*Ping)(nil), // 2: p2p.Ping - (*SubnetUptime)(nil), // 3: p2p.SubnetUptime - (*Pong)(nil), // 4: p2p.Pong - (*Handshake)(nil), // 5: p2p.Handshake - (*Client)(nil), // 6: p2p.Client - (*BloomFilter)(nil), // 7: p2p.BloomFilter - (*ClaimedIpPort)(nil), // 8: p2p.ClaimedIpPort - (*GetPeerList)(nil), // 9: p2p.GetPeerList - (*PeerList)(nil), // 10: p2p.PeerList - (*GetStateSummaryFrontier)(nil), // 11: p2p.GetStateSummaryFrontier - (*StateSummaryFrontier)(nil), // 12: p2p.StateSummaryFrontier - (*GetAcceptedStateSummary)(nil), // 13: p2p.GetAcceptedStateSummary - (*AcceptedStateSummary)(nil), // 14: p2p.AcceptedStateSummary - (*GetAcceptedFrontier)(nil), // 15: p2p.GetAcceptedFrontier - (*AcceptedFrontier)(nil), // 16: p2p.AcceptedFrontier - (*GetAccepted)(nil), // 17: p2p.GetAccepted - (*Accepted)(nil), // 18: p2p.Accepted - (*GetAncestors)(nil), // 19: p2p.GetAncestors - (*Ancestors)(nil), // 20: p2p.Ancestors - (*Get)(nil), // 21: p2p.Get - (*Put)(nil), // 22: p2p.Put - (*PushQuery)(nil), // 23: p2p.PushQuery - (*PullQuery)(nil), // 24: p2p.PullQuery - (*Chits)(nil), // 25: p2p.Chits - (*AppRequest)(nil), // 26: p2p.AppRequest - (*AppResponse)(nil), // 27: p2p.AppResponse - (*AppError)(nil), // 28: p2p.AppError - (*AppGossip)(nil), // 29: p2p.AppGossip + (*Pong)(nil), // 3: p2p.Pong + (*Handshake)(nil), // 4: p2p.Handshake + (*Client)(nil), // 5: p2p.Client + (*BloomFilter)(nil), // 6: p2p.BloomFilter + (*ClaimedIpPort)(nil), // 7: p2p.ClaimedIpPort + (*GetPeerList)(nil), // 8: p2p.GetPeerList + (*PeerList)(nil), // 9: p2p.PeerList + (*GetStateSummaryFrontier)(nil), // 10: p2p.GetStateSummaryFrontier + (*StateSummaryFrontier)(nil), // 11: p2p.StateSummaryFrontier + (*GetAcceptedStateSummary)(nil), // 12: p2p.GetAcceptedStateSummary + (*AcceptedStateSummary)(nil), // 13: p2p.AcceptedStateSummary + (*GetAcceptedFrontier)(nil), // 14: p2p.GetAcceptedFrontier + (*AcceptedFrontier)(nil), // 15: p2p.AcceptedFrontier + (*GetAccepted)(nil), // 16: p2p.GetAccepted + (*Accepted)(nil), // 17: p2p.Accepted + (*GetAncestors)(nil), // 18: p2p.GetAncestors + (*Ancestors)(nil), // 19: p2p.Ancestors + (*Get)(nil), // 20: p2p.Get + (*Put)(nil), // 21: p2p.Put + (*PushQuery)(nil), // 22: p2p.PushQuery + (*PullQuery)(nil), // 23: p2p.PullQuery + (*Chits)(nil), // 24: p2p.Chits + (*AppRequest)(nil), // 25: p2p.AppRequest + (*AppResponse)(nil), // 26: p2p.AppResponse + (*AppError)(nil), // 27: p2p.AppError + (*AppGossip)(nil), // 28: p2p.AppGossip } var file_p2p_p2p_proto_depIdxs = []int32{ 2, // 0: p2p.Message.ping:type_name -> p2p.Ping - 4, // 1: p2p.Message.pong:type_name -> p2p.Pong - 5, // 2: p2p.Message.handshake:type_name -> p2p.Handshake - 9, // 3: p2p.Message.get_peer_list:type_name -> p2p.GetPeerList - 10, // 4: p2p.Message.peer_list:type_name -> p2p.PeerList - 11, // 5: p2p.Message.get_state_summary_frontier:type_name -> p2p.GetStateSummaryFrontier - 12, // 6: p2p.Message.state_summary_frontier:type_name -> p2p.StateSummaryFrontier - 13, // 7: p2p.Message.get_accepted_state_summary:type_name -> p2p.GetAcceptedStateSummary - 14, // 8: p2p.Message.accepted_state_summary:type_name -> p2p.AcceptedStateSummary - 15, // 9: p2p.Message.get_accepted_frontier:type_name -> p2p.GetAcceptedFrontier - 16, // 10: p2p.Message.accepted_frontier:type_name -> p2p.AcceptedFrontier - 17, // 11: p2p.Message.get_accepted:type_name -> p2p.GetAccepted - 18, // 12: p2p.Message.accepted:type_name -> p2p.Accepted - 19, // 13: p2p.Message.get_ancestors:type_name -> p2p.GetAncestors - 20, // 14: p2p.Message.ancestors:type_name -> p2p.Ancestors - 21, // 15: p2p.Message.get:type_name -> p2p.Get - 22, // 16: p2p.Message.put:type_name -> p2p.Put - 23, // 17: p2p.Message.push_query:type_name -> p2p.PushQuery - 24, // 18: p2p.Message.pull_query:type_name -> p2p.PullQuery - 25, // 19: p2p.Message.chits:type_name -> p2p.Chits - 26, // 20: p2p.Message.app_request:type_name -> p2p.AppRequest - 27, // 21: p2p.Message.app_response:type_name -> p2p.AppResponse - 29, // 22: p2p.Message.app_gossip:type_name -> p2p.AppGossip - 28, // 23: p2p.Message.app_error:type_name -> p2p.AppError - 3, // 24: p2p.Ping.subnet_uptimes:type_name -> p2p.SubnetUptime - 6, // 25: p2p.Handshake.client:type_name -> p2p.Client - 7, // 26: p2p.Handshake.known_peers:type_name -> p2p.BloomFilter - 7, // 27: p2p.GetPeerList.known_peers:type_name -> p2p.BloomFilter - 8, // 28: p2p.PeerList.claimed_ip_ports:type_name -> p2p.ClaimedIpPort - 0, // 29: p2p.GetAncestors.engine_type:type_name -> p2p.EngineType - 30, // [30:30] is the sub-list for method output_type - 30, // [30:30] is the sub-list for method input_type - 30, // [30:30] is the sub-list for extension type_name - 30, // [30:30] is the sub-list for extension extendee - 0, // [0:30] is the sub-list for field type_name + 3, // 1: p2p.Message.pong:type_name -> p2p.Pong + 4, // 2: p2p.Message.handshake:type_name -> p2p.Handshake + 8, // 3: p2p.Message.get_peer_list:type_name -> p2p.GetPeerList + 9, // 4: p2p.Message.peer_list:type_name -> p2p.PeerList + 10, // 5: p2p.Message.get_state_summary_frontier:type_name -> p2p.GetStateSummaryFrontier + 11, // 6: p2p.Message.state_summary_frontier:type_name -> p2p.StateSummaryFrontier + 12, // 7: p2p.Message.get_accepted_state_summary:type_name -> p2p.GetAcceptedStateSummary + 13, // 8: p2p.Message.accepted_state_summary:type_name -> p2p.AcceptedStateSummary + 14, // 9: p2p.Message.get_accepted_frontier:type_name -> p2p.GetAcceptedFrontier + 15, // 10: p2p.Message.accepted_frontier:type_name -> p2p.AcceptedFrontier + 16, // 11: p2p.Message.get_accepted:type_name -> p2p.GetAccepted + 17, // 12: p2p.Message.accepted:type_name -> p2p.Accepted + 18, // 13: p2p.Message.get_ancestors:type_name -> p2p.GetAncestors + 19, // 14: p2p.Message.ancestors:type_name -> p2p.Ancestors + 20, // 15: p2p.Message.get:type_name -> p2p.Get + 21, // 16: p2p.Message.put:type_name -> p2p.Put + 22, // 17: p2p.Message.push_query:type_name -> p2p.PushQuery + 23, // 18: p2p.Message.pull_query:type_name -> p2p.PullQuery + 24, // 19: p2p.Message.chits:type_name -> p2p.Chits + 25, // 20: p2p.Message.app_request:type_name -> p2p.AppRequest + 26, // 21: p2p.Message.app_response:type_name -> p2p.AppResponse + 28, // 22: p2p.Message.app_gossip:type_name -> p2p.AppGossip + 27, // 23: p2p.Message.app_error:type_name -> p2p.AppError + 5, // 24: p2p.Handshake.client:type_name -> p2p.Client + 6, // 25: p2p.Handshake.known_peers:type_name -> p2p.BloomFilter + 6, // 26: p2p.GetPeerList.known_peers:type_name -> p2p.BloomFilter + 7, // 27: p2p.PeerList.claimed_ip_ports:type_name -> p2p.ClaimedIpPort + 0, // 28: p2p.GetAncestors.engine_type:type_name -> p2p.EngineType + 29, // [29:29] is the sub-list for method output_type + 29, // [29:29] is the sub-list for method input_type + 29, // [29:29] is the sub-list for extension type_name + 29, // [29:29] is the sub-list for extension extendee + 0, // [0:29] is the sub-list for field type_name } func init() { file_p2p_p2p_proto_init() } @@ -2980,18 +2903,6 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SubnetUptime); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_p2p_p2p_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Pong); i { case 0: return &v.state @@ -3003,7 +2914,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Handshake); i { case 0: return &v.state @@ -3015,7 +2926,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Client); i { case 0: return &v.state @@ -3027,7 +2938,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BloomFilter); i { case 0: return &v.state @@ -3039,7 +2950,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ClaimedIpPort); i { case 0: return &v.state @@ -3051,7 +2962,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetPeerList); i { case 0: return &v.state @@ -3063,7 +2974,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PeerList); i { case 0: return &v.state @@ -3075,7 +2986,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetStateSummaryFrontier); i { case 0: return &v.state @@ -3087,7 +2998,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StateSummaryFrontier); i { case 0: return &v.state @@ -3099,7 +3010,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetAcceptedStateSummary); i { case 0: return &v.state @@ -3111,7 +3022,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcceptedStateSummary); i { case 0: return &v.state @@ -3123,7 +3034,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetAcceptedFrontier); i { case 0: return &v.state @@ -3135,7 +3046,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcceptedFrontier); i { case 0: return &v.state @@ -3147,7 +3058,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetAccepted); i { case 0: return &v.state @@ -3159,7 +3070,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Accepted); i { case 0: return &v.state @@ -3171,7 +3082,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetAncestors); i { case 0: return &v.state @@ -3183,7 +3094,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Ancestors); i { case 0: return &v.state @@ -3195,7 +3106,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Get); i { case 0: return &v.state @@ -3207,7 +3118,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Put); i { case 0: return &v.state @@ -3219,7 +3130,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PushQuery); i { case 0: return &v.state @@ -3231,7 +3142,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PullQuery); i { case 0: return &v.state @@ -3243,7 +3154,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Chits); i { case 0: return &v.state @@ -3255,7 +3166,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AppRequest); i { case 0: return &v.state @@ -3267,7 +3178,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AppResponse); i { case 0: return &v.state @@ -3279,7 +3190,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AppError); i { case 0: return &v.state @@ -3291,7 +3202,7 @@ func file_p2p_p2p_proto_init() { return nil } } - file_p2p_p2p_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + file_p2p_p2p_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AppGossip); i { case 0: return &v.state @@ -3337,7 +3248,7 @@ func file_p2p_p2p_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_p2p_p2p_proto_rawDesc, NumEnums: 1, - NumMessages: 29, + NumMessages: 28, NumExtensions: 0, NumServices: 0, }, From a2f69d07c143400add24c2fc8ebc04e914e96769 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 25 Jul 2024 21:06:06 +0300 Subject: [PATCH 004/199] remove subnet uptimes from api --- api/info/client.go | 8 +++----- api/info/service.go | 9 ++------- api/info/service.md | 14 +------------- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/api/info/client.go b/api/info/client.go index 15812cd5c213..b4c6c58d31c6 100644 --- a/api/info/client.go +++ b/api/info/client.go @@ -27,7 +27,7 @@ type Client interface { Peers(context.Context, ...rpc.Option) ([]Peer, error) IsBootstrapped(context.Context, string, ...rpc.Option) (bool, error) GetTxFee(context.Context, ...rpc.Option) (*GetTxFeeResponse, error) - Uptime(context.Context, ids.ID, ...rpc.Option) (*UptimeResponse, error) + Uptime(context.Context, ...rpc.Option) (*UptimeResponse, error) GetVMs(context.Context, ...rpc.Option) (map[ids.ID][]string, error) } @@ -101,11 +101,9 @@ func (c *client) GetTxFee(ctx context.Context, options ...rpc.Option) (*GetTxFee return res, err } -func (c *client) Uptime(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (*UptimeResponse, error) { +func (c *client) Uptime(ctx context.Context, options ...rpc.Option) (*UptimeResponse, error) { res := &UptimeResponse{} - err := c.requester.SendRequest(ctx, "info.uptime", &UptimeRequest{ - SubnetID: subnetID, - }, res, options...) + err := c.requester.SendRequest(ctx, "info.uptime", struct{}{}, res, options...) return res, err } diff --git a/api/info/service.go b/api/info/service.go index fd0117c5a088..5f9e4b73934d 100644 --- a/api/info/service.go +++ b/api/info/service.go @@ -307,18 +307,13 @@ type UptimeResponse struct { WeightedAveragePercentage json.Float64 `json:"weightedAveragePercentage"` } -type UptimeRequest struct { - // if omitted, defaults to primary network - SubnetID ids.ID `json:"subnetID"` -} - -func (i *Info) Uptime(_ *http.Request, args *UptimeRequest, reply *UptimeResponse) error { +func (i *Info) Uptime(_ *http.Request, _ *struct{}, reply *UptimeResponse) error { i.log.Debug("API called", zap.String("service", "info"), zap.String("method", "uptime"), ) - result, err := i.networking.NodeUptime(args.SubnetID) + result, err := i.networking.NodeUptime() if err != nil { return fmt.Errorf("couldn't get node uptime: %w", err) } diff --git a/api/info/service.md b/api/info/service.md index d7e70e269dff..f3d56bdf1061 100644 --- a/api/info/service.md +++ b/api/info/service.md @@ -526,7 +526,6 @@ info.peers({ lastReceived: string, benched: string[], observedUptime: int, - observedSubnetUptime: map[string]int, } } ``` @@ -542,7 +541,6 @@ info.peers({ - `lastReceived` is the timestamp of last message received from the peer. - `benched` shows chain IDs that the peer is being benched. - `observedUptime` is this node's primary network uptime, observed by the peer. -- `observedSubnetUptime` is a map of Subnet IDs to this node's Subnet uptimes, observed by the peer. **Example Call:** @@ -575,7 +573,6 @@ curl -X POST --data '{ "lastReceived": "2020-06-01T15:22:57Z", "benched": [], "observedUptime": "99", - "observedSubnetUptimes": {}, "trackedSubnets": [], "benched": [] }, @@ -588,9 +585,6 @@ curl -X POST --data '{ "lastReceived": "2020-06-01T15:22:34Z", "benched": [], "observedUptime": "75", - "observedSubnetUptimes": { - "29uVeLPJB1eQJkzRemU8g8wZDw5uJRqpab5U2mX9euieVwiEbL": "100" - }, "trackedSubnets": [ "29uVeLPJB1eQJkzRemU8g8wZDw5uJRqpab5U2mX9euieVwiEbL" ], @@ -605,7 +599,6 @@ curl -X POST --data '{ "lastReceived": "2020-06-01T15:22:55Z", "benched": [], "observedUptime": "95", - "observedSubnetUptimes": {}, "trackedSubnets": [], "benched": [] } @@ -623,18 +616,13 @@ Other sources may be using data gathered with incomplete (limited) information. **Signature:** ```sh -info.uptime({ - subnetID: string // optional -}) -> +info.uptime() -> { rewardingStakePercentage: float64, weightedAveragePercentage: float64 } ``` -- `subnetID` is the Subnet to get the uptime of. If not provided, returns the uptime of the node on - the primary network. - - `rewardingStakePercentage` is the percent of stake which thinks this node is above the uptime requirement. - `weightedAveragePercentage` is the stake-weighted average of all observed uptimes for this node. From ee287c6d0868df860b9542fc3a40f5512ffb6f4b Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 25 Jul 2024 21:06:16 +0300 Subject: [PATCH 005/199] add tracked bool --- snow/uptime/locked_calculator_test.go | 6 +++--- snow/uptime/manager.go | 18 ++++++++++++++++++ snow/uptime/mock_calculator.go | 24 ++++++++++++------------ 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/snow/uptime/locked_calculator_test.go b/snow/uptime/locked_calculator_test.go index 9e5edaad8c63..c65fb084cddb 100644 --- a/snow/uptime/locked_calculator_test.go +++ b/snow/uptime/locked_calculator_test.go @@ -50,15 +50,15 @@ func TestLockedCalculator(t *testing.T) { isBootstrapped.Set(true) // Should return the value from the mocked inner calculator - mockCalc.EXPECT().CalculateUptime(gomock.Any(), gomock.Any()).AnyTimes().Return(time.Duration(0), time.Time{}, errTest) + mockCalc.EXPECT().CalculateUptime(gomock.Any()).AnyTimes().Return(time.Duration(0), time.Time{}, errTest) _, _, err = lc.CalculateUptime(nodeID) require.ErrorIs(err, errTest) - mockCalc.EXPECT().CalculateUptimePercent(gomock.Any(), gomock.Any()).AnyTimes().Return(float64(0), errTest) + mockCalc.EXPECT().CalculateUptimePercent(gomock.Any()).AnyTimes().Return(float64(0), errTest) _, err = lc.CalculateUptimePercent(nodeID) require.ErrorIs(err, errTest) - mockCalc.EXPECT().CalculateUptimePercentFrom(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(float64(0), errTest) + mockCalc.EXPECT().CalculateUptimePercentFrom(gomock.Any(), gomock.Any()).AnyTimes().Return(float64(0), errTest) _, err = lc.CalculateUptimePercentFrom(nodeID, time.Now()) require.ErrorIs(err, errTest) } diff --git a/snow/uptime/manager.go b/snow/uptime/manager.go index 2a915da3fb6c..93aaa4a1b139 100644 --- a/snow/uptime/manager.go +++ b/snow/uptime/manager.go @@ -40,6 +40,7 @@ type manager struct { state State connections map[ids.NodeID]time.Time // nodeID -> time + tracked bool } func NewManager(state State, clk *mockable.Clock) Manager { @@ -70,10 +71,18 @@ func (m *manager) StartTracking(nodeIDs []ids.NodeID) error { return err } } + m.tracked = true return nil } func (m *manager) StopTracking(nodeIDs []ids.NodeID) error { + // TODO: this was not here before, should we add it? + if !m.tracked { + return nil + } + defer func() { + m.tracked = false + }() now := m.clock.UnixTime() for _, nodeID := range nodeIDs { // If the node is already connected, then we can just @@ -139,6 +148,12 @@ func (m *manager) CalculateUptime(nodeID ids.NodeID) (time.Duration, time.Time, return upDuration, lastUpdated, nil } + if !m.tracked { + durationOffline := now.Sub(lastUpdated) + newUpDuration := upDuration + durationOffline + return newUpDuration, now, nil + } + timeConnected, isConnected := m.connections[nodeID] if !isConnected { return upDuration, now, nil @@ -187,6 +202,9 @@ func (m *manager) CalculateUptimePercentFrom(nodeID ids.NodeID, startTime time.T // updateUptime updates the uptime of the node on the state by the amount // of time that the node has been connected. func (m *manager) updateUptime(nodeID ids.NodeID) error { + if !m.tracked { + return nil + } newDuration, newLastUpdated, err := m.CalculateUptime(nodeID) if err == database.ErrNotFound { // If a non-validator disconnects, we don't care diff --git a/snow/uptime/mock_calculator.go b/snow/uptime/mock_calculator.go index cc5b5942e639..5e2485cc6ddb 100644 --- a/snow/uptime/mock_calculator.go +++ b/snow/uptime/mock_calculator.go @@ -41,9 +41,9 @@ func (m *MockCalculator) EXPECT() *MockCalculatorMockRecorder { } // CalculateUptime mocks base method. -func (m *MockCalculator) CalculateUptime(arg0 ids.NodeID, arg1 ids.ID) (time.Duration, time.Time, error) { +func (m *MockCalculator) CalculateUptime(arg0 ids.NodeID) (time.Duration, time.Time, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CalculateUptime", arg0, arg1) + ret := m.ctrl.Call(m, "CalculateUptime", arg0) ret0, _ := ret[0].(time.Duration) ret1, _ := ret[1].(time.Time) ret2, _ := ret[2].(error) @@ -51,37 +51,37 @@ func (m *MockCalculator) CalculateUptime(arg0 ids.NodeID, arg1 ids.ID) (time.Dur } // CalculateUptime indicates an expected call of CalculateUptime. -func (mr *MockCalculatorMockRecorder) CalculateUptime(arg0, arg1 any) *gomock.Call { +func (mr *MockCalculatorMockRecorder) CalculateUptime(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateUptime", reflect.TypeOf((*MockCalculator)(nil).CalculateUptime), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateUptime", reflect.TypeOf((*MockCalculator)(nil).CalculateUptime), arg0) } // CalculateUptimePercent mocks base method. -func (m *MockCalculator) CalculateUptimePercent(arg0 ids.NodeID, arg1 ids.ID) (float64, error) { +func (m *MockCalculator) CalculateUptimePercent(arg0 ids.NodeID) (float64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CalculateUptimePercent", arg0, arg1) + ret := m.ctrl.Call(m, "CalculateUptimePercent", arg0) ret0, _ := ret[0].(float64) ret1, _ := ret[1].(error) return ret0, ret1 } // CalculateUptimePercent indicates an expected call of CalculateUptimePercent. -func (mr *MockCalculatorMockRecorder) CalculateUptimePercent(arg0, arg1 any) *gomock.Call { +func (mr *MockCalculatorMockRecorder) CalculateUptimePercent(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateUptimePercent", reflect.TypeOf((*MockCalculator)(nil).CalculateUptimePercent), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateUptimePercent", reflect.TypeOf((*MockCalculator)(nil).CalculateUptimePercent), arg0) } // CalculateUptimePercentFrom mocks base method. -func (m *MockCalculator) CalculateUptimePercentFrom(arg0 ids.NodeID, arg1 ids.ID, arg2 time.Time) (float64, error) { +func (m *MockCalculator) CalculateUptimePercentFrom(arg0 ids.NodeID, arg1 time.Time) (float64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CalculateUptimePercentFrom", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "CalculateUptimePercentFrom", arg0, arg1) ret0, _ := ret[0].(float64) ret1, _ := ret[1].(error) return ret0, ret1 } // CalculateUptimePercentFrom indicates an expected call of CalculateUptimePercentFrom. -func (mr *MockCalculatorMockRecorder) CalculateUptimePercentFrom(arg0, arg1, arg2 any) *gomock.Call { +func (mr *MockCalculatorMockRecorder) CalculateUptimePercentFrom(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateUptimePercentFrom", reflect.TypeOf((*MockCalculator)(nil).CalculateUptimePercentFrom), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateUptimePercentFrom", reflect.TypeOf((*MockCalculator)(nil).CalculateUptimePercentFrom), arg0, arg1) } From a42e48cff65d5538fd535ddda18591f4702a787b Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 26 Jul 2024 16:10:18 +0300 Subject: [PATCH 006/199] remove unnecessary err --- network/network.go | 1 - 1 file changed, 1 deletion(-) diff --git a/network/network.go b/network/network.go index e92bc248a218..6567454cf6c2 100644 --- a/network/network.go +++ b/network/network.go @@ -52,7 +52,6 @@ var ( _ Network = (*network)(nil) errNotValidator = errors.New("node is not a validator") - errNotTracked = errors.New("subnet is not tracked") errExpectedProxy = errors.New("expected proxy") errExpectedTCPProtocol = errors.New("expected TCP protocol") ) From 4c542af29714708692f2ca8342e89a26ba25c90e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sat, 27 Jul 2024 00:02:49 +0300 Subject: [PATCH 007/199] remove connected subnet msg --- chains/manager.go | 11 +- message/internal_msg_builder.go | 26 ---- message/ops.go | 5 - snow/networking/handler/handler.go | 7 - snow/networking/handler/handler_test.go | 99 -------------- snow/networking/handler/health_test.go | 1 - snow/networking/handler/message_queue.go | 2 +- snow/networking/router/chain_router.go | 61 --------- snow/networking/router/chain_router_test.go | 123 ------------------ snow/networking/sender/sender_test.go | 3 - snow/validators/mock_subnet_connector.go | 55 -------- snow/validators/subnet_connector.go | 16 --- snow/validators/unhandled_subnet_connector.go | 23 ---- vms/platformvm/vm.go | 11 +- vms/platformvm/vm_test.go | 1 - 15 files changed, 5 insertions(+), 439 deletions(-) delete mode 100644 snow/validators/mock_subnet_connector.go delete mode 100644 snow/validators/subnet_connector.go delete mode 100644 snow/validators/unhandled_subnet_connector.go diff --git a/chains/manager.go b/chains/manager.go index bdc6d0ef0180..a87396d1b65e 100644 --- a/chains/manager.go +++ b/chains/manager.go @@ -897,7 +897,6 @@ func (m *manager) createAvalancheChain( m.FrontierPollFrequency, m.ConsensusAppConcurrency, m.ResourceTracker, - validators.UnhandledSubnetConnector, // avalanche chains don't use subnet connector sb, connectedValidators, peerTracker, @@ -1108,8 +1107,7 @@ func (m *manager) createSnowmanChain( } var ( - bootstrapFunc func() - subnetConnector = validators.UnhandledSubnetConnector + bootstrapFunc func() ) // If [m.validatorState] is nil then we are creating the P-Chain. Since the // P-Chain is the first chain to be created, we can use it to initialize @@ -1147,12 +1145,6 @@ func (m *manager) createSnowmanChain( bootstrapFunc = func() { close(m.unblockChainCreatorCh) } - - // Set up the subnet connector for the P-Chain - subnetConnector, ok = vm.(validators.SubnetConnector) - if !ok { - return nil, fmt.Errorf("expected validators.SubnetConnector but got %T", vm) - } } // Initialize the ProposerVM and the vm wrapped inside it @@ -1295,7 +1287,6 @@ func (m *manager) createSnowmanChain( m.FrontierPollFrequency, m.ConsensusAppConcurrency, m.ResourceTracker, - subnetConnector, sb, connectedValidators, peerTracker, diff --git a/message/internal_msg_builder.go b/message/internal_msg_builder.go index 141dabaae414..bd71b60bf4ca 100644 --- a/message/internal_msg_builder.go +++ b/message/internal_msg_builder.go @@ -493,32 +493,6 @@ func InternalConnected(nodeID ids.NodeID, nodeVersion *version.Application) Inbo } } -// ConnectedSubnet contains the subnet ID of the subnet that the node is -// connected to. -type ConnectedSubnet struct { - SubnetID ids.ID `json:"subnet_id,omitempty"` -} - -func (m *ConnectedSubnet) String() string { - return fmt.Sprintf( - "SubnetID: %s", - m.SubnetID, - ) -} - -// InternalConnectedSubnet returns a message that indicates the node with [nodeID] is -// connected to the subnet with the given [subnetID]. -func InternalConnectedSubnet(nodeID ids.NodeID, subnetID ids.ID) InboundMessage { - return &inboundMessage{ - nodeID: nodeID, - op: ConnectedSubnetOp, - message: &ConnectedSubnet{ - SubnetID: subnetID, - }, - expiration: mockable.MaxTime, - } -} - type Disconnected struct{} func (Disconnected) String() string { diff --git a/message/ops.go b/message/ops.go index 11c69087b5f8..4e6fff95426a 100644 --- a/message/ops.go +++ b/message/ops.go @@ -60,7 +60,6 @@ const ( CrossChainAppResponseOp // Internal: ConnectedOp - ConnectedSubnetOp DisconnectedOp NotifyOp GossipRequestOp @@ -120,7 +119,6 @@ var ( CrossChainAppErrorOp, CrossChainAppResponseOp, ConnectedOp, - ConnectedSubnetOp, DisconnectedOp, NotifyOp, GossipRequestOp, @@ -158,7 +156,6 @@ var ( ChitsOp, // Internal ConnectedOp, - ConnectedSubnetOp, DisconnectedOp, } @@ -281,8 +278,6 @@ func (op Op) String() string { // Internal case ConnectedOp: return "connected" - case ConnectedSubnetOp: - return "connected_subnet" case DisconnectedOp: return "disconnected" case NotifyOp: diff --git a/snow/networking/handler/handler.go b/snow/networking/handler/handler.go index 1eb42ca0dcdc..c9176ef92343 100644 --- a/snow/networking/handler/handler.go +++ b/snow/networking/handler/handler.go @@ -118,8 +118,6 @@ type handler struct { // Closed when this handler and [engine] are done shutting down closed chan struct{} - subnetConnector validators.SubnetConnector - subnet subnets.Subnet // Tracks the peers that are currently connected to this subnet @@ -136,7 +134,6 @@ func New( gossipFrequency time.Duration, threadPoolSize int, resourceTracker tracker.ResourceTracker, - subnetConnector validators.SubnetConnector, subnet subnets.Subnet, peerTracker commontracker.Peers, p2pTracker *p2p.PeerTracker, @@ -152,7 +149,6 @@ func New( closingChan: make(chan struct{}), closed: make(chan struct{}), resourceTracker: resourceTracker, - subnetConnector: subnetConnector, subnet: subnet, peerTracker: peerTracker, p2pTracker: p2pTracker, @@ -769,9 +765,6 @@ func (h *handler) handleSyncMsg(ctx context.Context, msg Message) error { h.p2pTracker.Connected(nodeID, msg.NodeVersion) return engine.Connected(ctx, nodeID, msg.NodeVersion) - case *message.ConnectedSubnet: - return h.subnetConnector.ConnectedSubnet(ctx, nodeID, msg.SubnetID) - case *message.Disconnected: err := h.peerTracker.Disconnected(ctx, nodeID) if err != nil { diff --git a/snow/networking/handler/handler_test.go b/snow/networking/handler/handler_test.go index 929c51780c24..095d09592cec 100644 --- a/snow/networking/handler/handler_test.go +++ b/snow/networking/handler/handler_test.go @@ -12,7 +12,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/message" @@ -73,7 +72,6 @@ func TestHandlerDropsTimedOutMessages(t *testing.T) { time.Second, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, subnets.New(ctx.NodeID, subnets.Config{}), commontracker.NewPeers(), peerTracker, @@ -180,7 +178,6 @@ func TestHandlerClosesOnError(t *testing.T) { time.Second, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, subnets.New(ctx.NodeID, subnets.Config{}), commontracker.NewPeers(), peerTracker, @@ -283,7 +280,6 @@ func TestHandlerDropsGossipDuringBootstrapping(t *testing.T) { 1, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, subnets.New(ctx.NodeID, subnets.Config{}), commontracker.NewPeers(), peerTracker, @@ -374,7 +370,6 @@ func TestHandlerDispatchInternal(t *testing.T) { time.Second, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, subnets.New(ctx.NodeID, subnets.Config{}), commontracker.NewPeers(), peerTracker, @@ -423,98 +418,6 @@ func TestHandlerDispatchInternal(t *testing.T) { wg.Wait() } -func TestHandlerSubnetConnector(t *testing.T) { - require := require.New(t) - - snowCtx := snowtest.Context(t, snowtest.CChainID) - ctx := snowtest.ConsensusContext(snowCtx) - vdrs := validators.NewManager() - require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) - - resourceTracker, err := tracker.NewResourceTracker( - prometheus.NewRegistry(), - resource.NoUsage, - meter.ContinuousFactory{}, - time.Second, - ) - require.NoError(err) - ctrl := gomock.NewController(t) - connector := validators.NewMockSubnetConnector(ctrl) - - nodeID := ids.GenerateTestNodeID() - subnetID := ids.GenerateTestID() - - peerTracker, err := p2p.NewPeerTracker( - logging.NoLog{}, - "", - prometheus.NewRegistry(), - nil, - version.CurrentApp, - ) - require.NoError(err) - - handler, err := New( - ctx, - vdrs, - nil, - time.Second, - testThreadPoolSize, - resourceTracker, - connector, - subnets.New(ctx.NodeID, subnets.Config{}), - commontracker.NewPeers(), - peerTracker, - prometheus.NewRegistry(), - ) - require.NoError(err) - - bootstrapper := &common.BootstrapperTest{ - EngineTest: common.EngineTest{ - T: t, - }, - } - bootstrapper.Default(false) - - engine := &common.EngineTest{T: t} - engine.Default(false) - engine.ContextF = func() *snow.ConsensusContext { - return ctx - } - - handler.SetEngineManager(&EngineManager{ - Snowman: &Engine{ - Bootstrapper: bootstrapper, - Consensus: engine, - }, - }) - ctx.State.Set(snow.EngineState{ - Type: p2ppb.EngineType_ENGINE_TYPE_SNOWMAN, - State: snow.NormalOp, // assumed bootstrap is done - }) - - bootstrapper.StartF = func(context.Context, uint32) error { - return nil - } - - handler.Start(context.Background(), false) - - // Handler should call subnet connector when ConnectedSubnet message is received - var wg sync.WaitGroup - connector.EXPECT().ConnectedSubnet(gomock.Any(), nodeID, subnetID).Do( - func(context.Context, ids.NodeID, ids.ID) { - wg.Done() - }) - - wg.Add(1) - defer wg.Wait() - - subnetInboundMessage := Message{ - InboundMessage: message.InternalConnectedSubnet(nodeID, subnetID), - EngineType: p2ppb.EngineType_ENGINE_TYPE_UNSPECIFIED, - } - handler.Push(context.Background(), subnetInboundMessage) -} - // Tests that messages are routed to the correct engine type func TestDynamicEngineTypeDispatch(t *testing.T) { tests := []struct { @@ -642,7 +545,6 @@ func TestDynamicEngineTypeDispatch(t *testing.T) { time.Second, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, subnets.New(ids.EmptyNodeID, subnets.Config{}), commontracker.NewPeers(), peerTracker, @@ -725,7 +627,6 @@ func TestHandlerStartError(t *testing.T) { time.Second, testThreadPoolSize, resourceTracker, - nil, subnets.New(ctx.NodeID, subnets.Config{}), commontracker.NewPeers(), peerTracker, diff --git a/snow/networking/handler/health_test.go b/snow/networking/handler/health_test.go index 789d3464187e..a1cce533beee 100644 --- a/snow/networking/handler/health_test.go +++ b/snow/networking/handler/health_test.go @@ -89,7 +89,6 @@ func TestHealthCheckSubnet(t *testing.T) { time.Second, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, sb, peerTracker, p2pTracker, diff --git a/snow/networking/handler/message_queue.go b/snow/networking/handler/message_queue.go index 4d632c62d77e..fbf362c86f73 100644 --- a/snow/networking/handler/message_queue.go +++ b/snow/networking/handler/message_queue.go @@ -203,7 +203,7 @@ func (m *messageQueue) Shutdown() { // canPop will return true for at least one message in [m.msgs] func (m *messageQueue) canPop(msg message.InboundMessage) bool { // Always pop connected and disconnected messages. - if op := msg.Op(); op == message.ConnectedOp || op == message.DisconnectedOp || op == message.ConnectedSubnetOp { + if op := msg.Op(); op == message.ConnectedOp || op == message.DisconnectedOp { return true } diff --git a/snow/networking/router/chain_router.go b/snow/networking/router/chain_router.go index 6af0984afc3f..b125800d5ff8 100644 --- a/snow/networking/router/chain_router.go +++ b/snow/networking/router/chain_router.go @@ -50,9 +50,6 @@ type peer struct { version *version.Application // The subnets that this peer is currently tracking trackedSubnets set.Set[ids.ID] - // The subnets that this peer actually has a connection to. - // This is a subset of trackedSubnets. - connectedSubnets set.Set[ids.ID] } // ChainRouter routes incoming messages from the validator network @@ -467,11 +464,6 @@ func (cr *ChainRouter) AddChain(ctx context.Context, chain handler.Handler) { if _, benched := cr.benched[cr.myNodeID]; benched { return } - - myself := cr.peers[cr.myNodeID] - for subnetID := range myself.trackedSubnets { - cr.connectedSubnet(myself, cr.myNodeID, subnetID) - } } // Connected routes an incoming notification that a validator was just connected @@ -526,7 +518,6 @@ func (cr *ChainRouter) Connected(nodeID ids.NodeID, nodeVersion *version.Applica } } - cr.connectedSubnet(connectedPeer, nodeID, subnetID) } // Disconnected routes an incoming notification that a validator was connected @@ -603,8 +594,6 @@ func (cr *ChainRouter) Benched(chainID ids.ID, nodeID ids.NodeID) { }) } } - - peer.connectedSubnets.Clear() } // Unbenched routes an incoming notification that a validator was just unbenched @@ -647,13 +636,6 @@ func (cr *ChainRouter) Unbenched(chainID ids.ID, nodeID ids.NodeID) { }) } } - - // This will unbench the node from all its subnets. - // We handle this case separately because the node may have been benched on - // a subnet that has no chains. - for subnetID := range peer.trackedSubnets { - cr.connectedSubnet(peer, nodeID, subnetID) - } } // HealthCheck returns results of router health checks. Returns: @@ -758,46 +740,3 @@ func (cr *ChainRouter) clearRequest( cr.metrics.outstandingRequests.Set(float64(cr.timedRequests.Len())) return uniqueRequestID, &request } - -// connectedSubnet pushes an InternalSubnetConnected message with [nodeID] and -// [subnetID] to the P-chain. This should be called when a node is either first -// connecting to [subnetID] or when a node that was already connected is -// unbenched on [subnetID]. This is a noop if [subnetID] is the Primary Network -// or if the peer is already marked as connected to the subnet. -// Invariant: should be called after *message.Connected is pushed to the P-chain -// Invariant: should be called after the P-chain was provided in [AddChain] -func (cr *ChainRouter) connectedSubnet(peer *peer, nodeID ids.NodeID, subnetID ids.ID) { - // if connected to primary network, we can skip this - // because Connected has its own internal message - if subnetID == constants.PrimaryNetworkID { - return - } - - // peer already connected to this subnet - if peer.connectedSubnets.Contains(subnetID) { - return - } - - msg := message.InternalConnectedSubnet(nodeID, subnetID) - // We only push this message to the P-chain because it is the only chain - // that cares about the connectivity of all subnets. Others chains learn - // about the connectivity of their own subnet when they receive a - // *message.Connected. - platformChain, ok := cr.chainHandlers[constants.PlatformChainID] - if !ok { - cr.log.Error("trying to issue InternalConnectedSubnet message, but platform chain is not registered", - zap.Stringer("nodeID", nodeID), - zap.Stringer("subnetID", subnetID), - ) - return - } - platformChain.Push( - context.TODO(), - handler.Message{ - InboundMessage: msg, - EngineType: p2p.EngineType_ENGINE_TYPE_UNSPECIFIED, - }, - ) - - peer.connectedSubnets.Add(subnetID) -} diff --git a/snow/networking/router/chain_router_test.go b/snow/networking/router/chain_router_test.go index 7472de1fc016..2039fa46c52c 100644 --- a/snow/networking/router/chain_router_test.go +++ b/snow/networking/router/chain_router_test.go @@ -25,7 +25,6 @@ import ( "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/subnets" - "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/math/meter" "github.com/ava-labs/avalanchego/utils/resource" @@ -109,7 +108,6 @@ func TestShutdown(t *testing.T) { time.Second, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, subnets.New(chainCtx.NodeID, subnets.Config{}), commontracker.NewPeers(), p2pTracker, @@ -235,7 +233,6 @@ func TestConnectedAfterShutdownErrorLogRegression(t *testing.T) { time.Second, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, subnets.New(chainCtx.NodeID, subnets.Config{}), commontracker.NewPeers(), p2pTracker, @@ -368,7 +365,6 @@ func TestShutdownTimesOut(t *testing.T) { time.Second, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, subnets.New(ctx.NodeID, subnets.Config{}), commontracker.NewPeers(), p2pTracker, @@ -538,7 +534,6 @@ func TestRouterTimeout(t *testing.T) { time.Second, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, subnets.New(ctx.NodeID, subnets.Config{}), commontracker.NewPeers(), p2pTracker, @@ -1121,7 +1116,6 @@ func TestValidatorOnlyMessageDrops(t *testing.T) { time.Second, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, sb, commontracker.NewPeers(), p2pTracker, @@ -1211,121 +1205,6 @@ func TestValidatorOnlyMessageDrops(t *testing.T) { require.True(calledF) // should be called since this is a validator request } -func TestConnectedSubnet(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - tm, err := timeout.NewManager( - &timer.AdaptiveTimeoutConfig{ - InitialTimeout: 3 * time.Second, - MinimumTimeout: 3 * time.Second, - MaximumTimeout: 5 * time.Minute, - TimeoutCoefficient: 1, - TimeoutHalflife: 5 * time.Minute, - }, - benchlist.NewNoBenchlist(), - prometheus.NewRegistry(), - prometheus.NewRegistry(), - ) - require.NoError(err) - - go tm.Dispatch() - defer tm.Stop() - - // Create chain router - myNodeID := ids.GenerateTestNodeID() - peerNodeID := ids.GenerateTestNodeID() - subnetID0 := ids.GenerateTestID() - subnetID1 := ids.GenerateTestID() - trackedSubnets := set.Of(subnetID0, subnetID1) - chainRouter := ChainRouter{} - require.NoError(chainRouter.Initialize( - myNodeID, - logging.NoLog{}, - tm, - time.Millisecond, - set.Set[ids.ID]{}, - true, - trackedSubnets, - nil, - HealthConfig{}, - prometheus.NewRegistry(), - )) - - // Create bootstrapper, engine and handler - snowCtx := snowtest.Context(t, snowtest.PChainID) - ctx := snowtest.ConsensusContext(snowCtx) - ctx.Executing.Set(false) - ctx.State.Set(snow.EngineState{ - Type: engineType, - State: snow.NormalOp, - }) - - myConnectedMsg := handler.Message{ - InboundMessage: message.InternalConnected(myNodeID, version.CurrentApp), - EngineType: p2ppb.EngineType_ENGINE_TYPE_UNSPECIFIED, - } - mySubnetConnectedMsg0 := handler.Message{ - InboundMessage: message.InternalConnectedSubnet(myNodeID, subnetID0), - EngineType: p2ppb.EngineType_ENGINE_TYPE_UNSPECIFIED, - } - mySubnetConnectedMsg1 := handler.Message{ - InboundMessage: message.InternalConnectedSubnet(myNodeID, subnetID1), - EngineType: p2ppb.EngineType_ENGINE_TYPE_UNSPECIFIED, - } - - platformHandler := handler.NewMockHandler(ctrl) - platformHandler.EXPECT().Context().Return(ctx).AnyTimes() - platformHandler.EXPECT().SetOnStopped(gomock.Any()).AnyTimes() - platformHandler.EXPECT().Push(gomock.Any(), myConnectedMsg).Times(1) - platformHandler.EXPECT().Push(gomock.Any(), mySubnetConnectedMsg0).Times(1) - platformHandler.EXPECT().Push(gomock.Any(), mySubnetConnectedMsg1).Times(1) - - chainRouter.AddChain(context.Background(), platformHandler) - - peerConnectedMsg := handler.Message{ - InboundMessage: message.InternalConnected(peerNodeID, version.CurrentApp), - EngineType: p2ppb.EngineType_ENGINE_TYPE_UNSPECIFIED, - } - platformHandler.EXPECT().Push(gomock.Any(), peerConnectedMsg).Times(1) - chainRouter.Connected(peerNodeID, version.CurrentApp, constants.PrimaryNetworkID) - - peerSubnetConnectedMsg0 := handler.Message{ - InboundMessage: message.InternalConnectedSubnet(peerNodeID, subnetID0), - EngineType: p2ppb.EngineType_ENGINE_TYPE_UNSPECIFIED, - } - platformHandler.EXPECT().Push(gomock.Any(), peerSubnetConnectedMsg0).Times(1) - chainRouter.Connected(peerNodeID, version.CurrentApp, subnetID0) - - myDisconnectedMsg := handler.Message{ - InboundMessage: message.InternalDisconnected(myNodeID), - EngineType: p2ppb.EngineType_ENGINE_TYPE_UNSPECIFIED, - } - platformHandler.EXPECT().Push(gomock.Any(), myDisconnectedMsg).Times(1) - chainRouter.Benched(constants.PlatformChainID, myNodeID) - - peerDisconnectedMsg := handler.Message{ - InboundMessage: message.InternalDisconnected(peerNodeID), - EngineType: p2ppb.EngineType_ENGINE_TYPE_UNSPECIFIED, - } - platformHandler.EXPECT().Push(gomock.Any(), peerDisconnectedMsg).Times(1) - chainRouter.Benched(constants.PlatformChainID, peerNodeID) - - platformHandler.EXPECT().Push(gomock.Any(), myConnectedMsg).Times(1) - platformHandler.EXPECT().Push(gomock.Any(), mySubnetConnectedMsg0).Times(1) - platformHandler.EXPECT().Push(gomock.Any(), mySubnetConnectedMsg1).Times(1) - - chainRouter.Unbenched(constants.PlatformChainID, myNodeID) - - platformHandler.EXPECT().Push(gomock.Any(), peerConnectedMsg).Times(1) - platformHandler.EXPECT().Push(gomock.Any(), peerSubnetConnectedMsg0).Times(1) - - chainRouter.Unbenched(constants.PlatformChainID, peerNodeID) - - platformHandler.EXPECT().Push(gomock.Any(), peerDisconnectedMsg).Times(1) - chainRouter.Disconnected(peerNodeID) -} - func TestValidatorOnlyAllowedNodeMessageDrops(t *testing.T) { require := require.New(t) @@ -1402,7 +1281,6 @@ func TestValidatorOnlyAllowedNodeMessageDrops(t *testing.T) { time.Second, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, sb, commontracker.NewPeers(), p2pTracker, @@ -1742,7 +1620,6 @@ func newChainRouterTest(t *testing.T) (*ChainRouter, *common.EngineTest) { time.Second, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, subnets.New(ctx.NodeID, subnets.Config{}), commontracker.NewPeers(), p2pTracker, diff --git a/snow/networking/sender/sender_test.go b/snow/networking/sender/sender_test.go index 34f138f6db21..0402bb636505 100644 --- a/snow/networking/sender/sender_test.go +++ b/snow/networking/sender/sender_test.go @@ -128,7 +128,6 @@ func TestTimeout(t *testing.T) { time.Hour, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, subnets.New(ctx.NodeID, subnets.Config{}), commontracker.NewPeers(), p2pTracker, @@ -405,7 +404,6 @@ func TestReliableMessages(t *testing.T) { 1, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, subnets.New(ctx.NodeID, subnets.Config{}), commontracker.NewPeers(), p2pTracker, @@ -562,7 +560,6 @@ func TestReliableMessagesToMyself(t *testing.T) { time.Second, testThreadPoolSize, resourceTracker, - validators.UnhandledSubnetConnector, subnets.New(ctx.NodeID, subnets.Config{}), commontracker.NewPeers(), p2pTracker, diff --git a/snow/validators/mock_subnet_connector.go b/snow/validators/mock_subnet_connector.go deleted file mode 100644 index b9f3ee0519b8..000000000000 --- a/snow/validators/mock_subnet_connector.go +++ /dev/null @@ -1,55 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/snow/validators (interfaces: SubnetConnector) -// -// Generated by this command: -// -// mockgen -package=validators -destination=snow/validators/mock_subnet_connector.go github.com/ava-labs/avalanchego/snow/validators SubnetConnector -// - -// Package validators is a generated GoMock package. -package validators - -import ( - context "context" - reflect "reflect" - - ids "github.com/ava-labs/avalanchego/ids" - gomock "go.uber.org/mock/gomock" -) - -// MockSubnetConnector is a mock of SubnetConnector interface. -type MockSubnetConnector struct { - ctrl *gomock.Controller - recorder *MockSubnetConnectorMockRecorder -} - -// MockSubnetConnectorMockRecorder is the mock recorder for MockSubnetConnector. -type MockSubnetConnectorMockRecorder struct { - mock *MockSubnetConnector -} - -// NewMockSubnetConnector creates a new mock instance. -func NewMockSubnetConnector(ctrl *gomock.Controller) *MockSubnetConnector { - mock := &MockSubnetConnector{ctrl: ctrl} - mock.recorder = &MockSubnetConnectorMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockSubnetConnector) EXPECT() *MockSubnetConnectorMockRecorder { - return m.recorder -} - -// ConnectedSubnet mocks base method. -func (m *MockSubnetConnector) ConnectedSubnet(arg0 context.Context, arg1 ids.NodeID, arg2 ids.ID) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ConnectedSubnet", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// ConnectedSubnet indicates an expected call of ConnectedSubnet. -func (mr *MockSubnetConnectorMockRecorder) ConnectedSubnet(arg0, arg1, arg2 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConnectedSubnet", reflect.TypeOf((*MockSubnetConnector)(nil).ConnectedSubnet), arg0, arg1, arg2) -} diff --git a/snow/validators/subnet_connector.go b/snow/validators/subnet_connector.go deleted file mode 100644 index 06b02ff90820..000000000000 --- a/snow/validators/subnet_connector.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package validators - -import ( - "context" - - "github.com/ava-labs/avalanchego/ids" -) - -// SubnetConnector represents a handler that is called when a connection is -// marked as connected to a subnet -type SubnetConnector interface { - ConnectedSubnet(ctx context.Context, nodeID ids.NodeID, subnetID ids.ID) error -} diff --git a/snow/validators/unhandled_subnet_connector.go b/snow/validators/unhandled_subnet_connector.go deleted file mode 100644 index 08447c4582ad..000000000000 --- a/snow/validators/unhandled_subnet_connector.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package validators - -import ( - "context" - "fmt" - - "github.com/ava-labs/avalanchego/ids" -) - -var UnhandledSubnetConnector SubnetConnector = &unhandledSubnetConnector{} - -type unhandledSubnetConnector struct{} - -func (unhandledSubnetConnector) ConnectedSubnet(_ context.Context, nodeID ids.NodeID, subnetID ids.ID) error { - return fmt.Errorf( - "unhandled ConnectedSubnet with nodeID=%q and subnetID=%q", - nodeID, - subnetID, - ) -} diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index 6690488ef291..8244afe97e42 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -53,10 +53,9 @@ import ( ) var ( - _ snowmanblock.ChainVM = (*VM)(nil) - _ secp256k1fx.VM = (*VM)(nil) - _ validators.State = (*VM)(nil) - _ validators.SubnetConnector = (*VM)(nil) + _ snowmanblock.ChainVM = (*VM)(nil) + _ secp256k1fx.VM = (*VM)(nil) + _ validators.State = (*VM)(nil) ) type VM struct { @@ -467,10 +466,6 @@ func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, version *version return vm.Network.Connected(ctx, nodeID, version) } -func (vm *VM) ConnectedSubnet(_ context.Context, nodeID ids.NodeID, subnetID ids.ID) error { - return nil -} - func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error { if err := vm.uptimeManager.Disconnect(nodeID); err != nil { return err diff --git a/vms/platformvm/vm_test.go b/vms/platformvm/vm_test.go index 277eed268013..5732b095e9f6 100644 --- a/vms/platformvm/vm_test.go +++ b/vms/platformvm/vm_test.go @@ -1542,7 +1542,6 @@ func TestBootstrapPartiallyAccepted(t *testing.T) { time.Hour, 2, cpuTracker, - vm, subnets.New(ctx.NodeID, subnets.Config{}), tracker.NewPeers(), peerTracker, From 9161864c3d83f76c16d2bbe70d09ec017b44ef49 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sat, 27 Jul 2024 00:42:52 +0300 Subject: [PATCH 008/199] fix linter --- chains/manager.go | 4 +--- scripts/mocks.mockgen.txt | 1 - snow/networking/router/chain_router.go | 15 --------------- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/chains/manager.go b/chains/manager.go index a87396d1b65e..590a88ae5ac9 100644 --- a/chains/manager.go +++ b/chains/manager.go @@ -1106,9 +1106,7 @@ func (m *manager) createSnowmanChain( messageSender = sender.Trace(messageSender, m.Tracer) } - var ( - bootstrapFunc func() - ) + var bootstrapFunc func() // If [m.validatorState] is nil then we are creating the P-Chain. Since the // P-Chain is the first chain to be created, we can use it to initialize // required interfaces for the other chains diff --git a/scripts/mocks.mockgen.txt b/scripts/mocks.mockgen.txt index 139252666ab0..fb370aebff96 100644 --- a/scripts/mocks.mockgen.txt +++ b/scripts/mocks.mockgen.txt @@ -17,7 +17,6 @@ github.com/ava-labs/avalanchego/snow/networking/tracker=Targeter=snow/networking github.com/ava-labs/avalanchego/snow/networking/tracker=Tracker=snow/networking/tracker/mock_resource_tracker.go github.com/ava-labs/avalanchego/snow/uptime=Calculator=snow/uptime/mock_calculator.go github.com/ava-labs/avalanchego/snow/validators=State=snow/validators/mock_state.go -github.com/ava-labs/avalanchego/snow/validators=SubnetConnector=snow/validators/mock_subnet_connector.go github.com/ava-labs/avalanchego/utils/crypto/keychain=Ledger=utils/crypto/keychain/mock_ledger.go github.com/ava-labs/avalanchego/utils/filesystem=Reader=utils/filesystem/mock_io.go github.com/ava-labs/avalanchego/utils/hashing=Hasher=utils/hashing/mock_hasher.go diff --git a/snow/networking/router/chain_router.go b/snow/networking/router/chain_router.go index b125800d5ff8..01b0137407c4 100644 --- a/snow/networking/router/chain_router.go +++ b/snow/networking/router/chain_router.go @@ -450,20 +450,6 @@ func (cr *ChainRouter) AddChain(ctx context.Context, chain handler.Handler) { }, ) } - - // When we register the P-chain, we mark ourselves as connected on all of - // the subnets that we have tracked. - if chainID != constants.PlatformChainID { - return - } - - // If we have currently benched ourselves, we will mark ourselves as - // connected when we unbench. So skip connecting now. - // This is not "theoretically" possible, but keeping this here prevents us - // from keeping an invariant that we never bench ourselves. - if _, benched := cr.benched[cr.myNodeID]; benched { - return - } } // Connected routes an incoming notification that a validator was just connected @@ -517,7 +503,6 @@ func (cr *ChainRouter) Connected(nodeID ids.NodeID, nodeVersion *version.Applica } } } - } // Disconnected routes an incoming notification that a validator was connected From f275c15575ebbcd97c9507205b272ed91f6d65d2 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 31 Jul 2024 17:23:33 +0300 Subject: [PATCH 009/199] rework on tests and reviews --- network/peer/peer_test.go | 4 +-- snow/uptime/manager.go | 14 ++++---- vms/platformvm/service.go | 26 +++++--------- vms/platformvm/service_test.go | 10 ++++++ vms/platformvm/state/state_test.go | 38 +++++++++++++++++++-- vms/platformvm/txs/executor/helpers_test.go | 5 --- 6 files changed, 64 insertions(+), 33 deletions(-) diff --git a/network/peer/peer_test.go b/network/peer/peer_test.go index ae2f86a74fc1..11b7b5590565 100644 --- a/network/peer/peer_test.go +++ b/network/peer/peer_test.go @@ -227,7 +227,7 @@ func TestPingUptimes(t *testing.T) { require.NoError(peer0.AwaitClosed(context.Background())) require.NoError(peer1.AwaitClosed(context.Background())) }() - pingMsg, err := sharedConfig.MessageCreator.Ping(0) + pingMsg, err := sharedConfig.MessageCreator.Ping(1) require.NoError(err) require.True(peer0.Send(context.Background(), pingMsg)) @@ -238,7 +238,7 @@ func TestPingUptimes(t *testing.T) { sendAndFlush(t, peer0, peer1) uptime := peer1.ObservedUptime() - require.Equal(uint32(0), uptime) + require.Equal(uint32(1), uptime) } func TestTrackedSubnets(t *testing.T) { diff --git a/snow/uptime/manager.go b/snow/uptime/manager.go index 93aaa4a1b139..52d4b4c7a088 100644 --- a/snow/uptime/manager.go +++ b/snow/uptime/manager.go @@ -40,7 +40,9 @@ type manager struct { state State connections map[ids.NodeID]time.Time // nodeID -> time - tracked bool + // Whether we have started tracking the uptime of the nodes + // This is used to avoid setting the uptime before we have started tracking + startedTracking bool } func NewManager(state State, clk *mockable.Clock) Manager { @@ -71,17 +73,17 @@ func (m *manager) StartTracking(nodeIDs []ids.NodeID) error { return err } } - m.tracked = true + m.startedTracking = true return nil } func (m *manager) StopTracking(nodeIDs []ids.NodeID) error { // TODO: this was not here before, should we add it? - if !m.tracked { + if !m.startedTracking { return nil } defer func() { - m.tracked = false + m.startedTracking = false }() now := m.clock.UnixTime() for _, nodeID := range nodeIDs { @@ -148,7 +150,7 @@ func (m *manager) CalculateUptime(nodeID ids.NodeID) (time.Duration, time.Time, return upDuration, lastUpdated, nil } - if !m.tracked { + if !m.startedTracking { durationOffline := now.Sub(lastUpdated) newUpDuration := upDuration + durationOffline return newUpDuration, now, nil @@ -202,7 +204,7 @@ func (m *manager) CalculateUptimePercentFrom(nodeID ids.NodeID, startTime time.T // updateUptime updates the uptime of the node on the state by the amount // of time that the node has been connected. func (m *manager) updateUptime(nodeID ids.NodeID) error { - if !m.tracked { + if !m.startedTracking { return nil } newDuration, newLastUpdated, err := m.CalculateUptime(nodeID) diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 1bb82e1a1203..501cb743fc6b 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -836,10 +836,17 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato // TODO: decide whether we want to keep connected for subnet validators // it should be available at this point if args.SubnetID == constants.PrimaryNetworkID { - currentUptime, isConnected, err := s.getAPIUptime(currentStaker) + rawUptime, err := s.vm.uptimeManager.CalculateUptimePercentFrom(currentStaker.NodeID, currentStaker.StartTime) if err != nil { return err } + // Transform this to a percentage (0-100) to make it consistent + // with observedUptime in info.peers API + currentUptime := avajson.Float32(rawUptime * 100) + if err != nil { + return err + } + isConnected := s.vm.uptimeManager.IsConnected(currentStaker.NodeID) connected = &isConnected uptime = ¤tUptime } @@ -1828,23 +1835,6 @@ func (s *Service) GetBlockByHeight(_ *http.Request, args *api.GetBlockByHeightAr return err } -// Returns: -// 1) the uptime of a validator in the API format -// 2) whether the validator is currently connected -// 3) an error if one occurred -func (s *Service) getAPIUptime(staker *state.Staker) (avajson.Float32, bool, error) { - rawUptime, err := s.vm.uptimeManager.CalculateUptimePercentFrom(staker.NodeID, staker.StartTime) - if err != nil { - return 0, false, err - } - connected := s.vm.uptimeManager.IsConnected(staker.NodeID) - - // Transform this to a percentage (0-100) to make it consistent - // with observedUptime in info.peers API - uptime := avajson.Float32(rawUptime * 100) - return uptime, connected, nil -} - func (s *Service) getAPIOwner(owner *secp256k1fx.OutputOwners) (*platformapi.Owner, error) { apiOwner := &platformapi.Owner{ Locktime: avajson.Uint64(owner.Locktime), diff --git a/vms/platformvm/service_test.go b/vms/platformvm/service_test.go index a3b2e743c9bc..1a688579b736 100644 --- a/vms/platformvm/service_test.go +++ b/vms/platformvm/service_test.go @@ -32,6 +32,8 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/formatting" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/block" "github.com/ava-labs/avalanchego/vms/platformvm/signer" @@ -602,6 +604,12 @@ func TestGetCurrentValidators(t *testing.T) { args := GetCurrentValidatorsArgs{SubnetID: constants.PrimaryNetworkID} response := GetCurrentValidatorsReply{} + connectedIDs := set.NewSet[ids.NodeID](len(genesis.Validators) - 1) + for _, vdr := range genesis.Validators[:len(genesis.Validators)-1] { + connectedIDs.Add(vdr.NodeID) + require.NoError(service.vm.Connected(context.Background(), vdr.NodeID, version.CurrentApp)) + } + require.NoError(service.GetCurrentValidators(nil, &args, &response)) require.Len(response.Validators, len(genesis.Validators)) @@ -615,6 +623,8 @@ func TestGetCurrentValidators(t *testing.T) { require.Equal(vdr.EndTime, gotVdr.EndTime) require.Equal(vdr.StartTime, gotVdr.StartTime) + require.Equal(connectedIDs.Contains(vdr.NodeID), *gotVdr.Connected) + require.EqualValues(100, *gotVdr.Uptime) found = true } require.True(found, "expected validators to contain %s but didn't", vdr.NodeID) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 517981552042..c5243c63bf10 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -106,6 +106,9 @@ func TestPersistStakers(t *testing.T) { // with the right weight and showing the BLS key checkValidatorsSet func(*require.Assertions, *state, *Staker) + // Check that node duly track stakers uptimes + checkValidatorUptimes func(*require.Assertions, *state, *Staker) + // Check whether weight/bls keys diffs are duly stored checkDiffs func(*require.Assertions, *state, *Staker, uint64) }{ @@ -156,6 +159,17 @@ func TestPersistStakers(t *testing.T) { Weight: staker.Weight, }, valOut) }, + checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { + upDuration, lastUpdated, err := s.GetUptime(staker.NodeID) + if staker.SubnetID != constants.PrimaryNetworkID { + // only primary network validators have uptimes + r.ErrorIs(err, database.ErrNotFound) + } else { + r.NoError(err) + r.Equal(upDuration, time.Duration(0)) + r.Equal(lastUpdated, staker.StartTime) + } + }, checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) r.NoError(err) @@ -252,6 +266,7 @@ func TestPersistStakers(t *testing.T) { r.Equal(valOut.NodeID, staker.NodeID) r.Equal(valOut.Weight, val.Weight+staker.Weight) }, + checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { // validator's weight must increase of delegator's weight amount weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) @@ -303,6 +318,11 @@ func TestPersistStakers(t *testing.T) { valsMap := s.cfg.Validators.GetMap(staker.SubnetID) r.Empty(valsMap) }, + checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { + // pending validators uptime is not tracked + _, _, err := s.GetUptime(staker.NodeID) + r.ErrorIs(err, database.ErrNotFound) + }, checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { // pending validators weight diff and bls diffs are not stored _, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) @@ -373,7 +393,8 @@ func TestPersistStakers(t *testing.T) { valsMap := s.cfg.Validators.GetMap(staker.SubnetID) r.Empty(valsMap) }, - checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, + checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, + checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, }, "delete current validator": { storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { @@ -419,6 +440,11 @@ func TestPersistStakers(t *testing.T) { valsMap := s.cfg.Validators.GetMap(staker.SubnetID) r.Empty(valsMap) }, + checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { + // uptimes of delete validators are dropped + _, _, err := s.GetUptime(staker.NodeID) + r.ErrorIs(err, database.ErrNotFound) + }, checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) r.NoError(err) @@ -515,6 +541,7 @@ func TestPersistStakers(t *testing.T) { r.Equal(valOut.NodeID, staker.NodeID) r.Equal(valOut.Weight, val.Weight) }, + checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { // validator's weight must decrease of delegator's weight amount weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) @@ -568,6 +595,10 @@ func TestPersistStakers(t *testing.T) { valsMap := s.cfg.Validators.GetMap(staker.SubnetID) r.Empty(valsMap) }, + checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { + _, _, err := s.GetUptime(staker.NodeID) + r.ErrorIs(err, database.ErrNotFound) + }, checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { _, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) r.ErrorIs(err, database.ErrNotFound) @@ -635,7 +666,8 @@ func TestPersistStakers(t *testing.T) { valsMap := s.cfg.Validators.GetMap(staker.SubnetID) r.Empty(valsMap) }, - checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, + checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, + checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, }, } @@ -653,6 +685,7 @@ func TestPersistStakers(t *testing.T) { // check all relevant data are stored test.checkStakerInState(require, state, staker) test.checkValidatorsSet(require, state, staker) + test.checkValidatorUptimes(require, state, staker) test.checkDiffs(require, state, staker, 0 /*height*/) // rebuild the state @@ -666,6 +699,7 @@ func TestPersistStakers(t *testing.T) { // check again that all relevant data are still available in rebuilt state test.checkStakerInState(require, state, staker) test.checkValidatorsSet(require, state, staker) + test.checkValidatorUptimes(require, state, staker) test.checkDiffs(require, state, staker, 0 /*height*/) }) } diff --git a/vms/platformvm/txs/executor/helpers_test.go b/vms/platformvm/txs/executor/helpers_test.go index a5f2d6fcca59..430644571ae2 100644 --- a/vms/platformvm/txs/executor/helpers_test.go +++ b/vms/platformvm/txs/executor/helpers_test.go @@ -189,11 +189,6 @@ func newEnvironment(t *testing.T, f fork) *environment { require.NoError(env.uptimes.StopTracking(validatorIDs)) - for subnetID := range env.config.TrackedSubnets { - validatorIDs := env.config.Validators.GetValidatorIDs(subnetID) - - require.NoError(env.uptimes.StopTracking(validatorIDs)) - } env.state.SetHeight(math.MaxUint64) require.NoError(env.state.Commit()) } From d9355cc0f5a634738272228a564c6ac85addbf75 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 31 Jul 2024 17:45:07 +0300 Subject: [PATCH 010/199] fix linter --- vms/platformvm/service_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/service_test.go b/vms/platformvm/service_test.go index 1a688579b736..447e21bcb901 100644 --- a/vms/platformvm/service_test.go +++ b/vms/platformvm/service_test.go @@ -624,7 +624,7 @@ func TestGetCurrentValidators(t *testing.T) { require.Equal(vdr.EndTime, gotVdr.EndTime) require.Equal(vdr.StartTime, gotVdr.StartTime) require.Equal(connectedIDs.Contains(vdr.NodeID), *gotVdr.Connected) - require.EqualValues(100, *gotVdr.Uptime) + require.Equal(avajson.Float32(100), *gotVdr.Uptime) found = true } require.True(found, "expected validators to contain %s but didn't", vdr.NodeID) From 46501ad6a3778ce636059dd3b0a8236494b07eec Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 6 Aug 2024 11:00:08 +0300 Subject: [PATCH 011/199] Update proto/p2p/p2p.proto Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur --- proto/p2p/p2p.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto/p2p/p2p.proto b/proto/p2p/p2p.proto index 5d0a3a31a1eb..a309226973ee 100644 --- a/proto/p2p/p2p.proto +++ b/proto/p2p/p2p.proto @@ -64,7 +64,7 @@ message Message { message Ping { // Uptime percentage on the primary network [0, 100] uint32 uptime = 1; - reserved 2; // Until E upgrade is activated. + reserved 2; // Until Etna upgrade is activated. } // Pong is sent in response to a Ping. From b7459bdf40e5042a52a869fbfc8cf93c15038793 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 6 Aug 2024 11:01:35 +0300 Subject: [PATCH 012/199] fix comment Signed-off-by: Ceyhun Onur --- snow/uptime/manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snow/uptime/manager.go b/snow/uptime/manager.go index 52d4b4c7a088..284fcdea7614 100644 --- a/snow/uptime/manager.go +++ b/snow/uptime/manager.go @@ -39,7 +39,7 @@ type manager struct { clock *mockable.Clock state State - connections map[ids.NodeID]time.Time // nodeID -> time + connections map[ids.NodeID]time.Time // nodeID -> connected at // Whether we have started tracking the uptime of the nodes // This is used to avoid setting the uptime before we have started tracking startedTracking bool From b81b73719cbe9b9fa6a70beb665cf9837447dce8 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 6 Aug 2024 11:20:05 +0300 Subject: [PATCH 013/199] Update vms/platformvm/service_test.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur --- vms/platformvm/service_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/vms/platformvm/service_test.go b/vms/platformvm/service_test.go index 447e21bcb901..588d15b3e72c 100644 --- a/vms/platformvm/service_test.go +++ b/vms/platformvm/service_test.go @@ -604,6 +604,7 @@ func TestGetCurrentValidators(t *testing.T) { args := GetCurrentValidatorsArgs{SubnetID: constants.PrimaryNetworkID} response := GetCurrentValidatorsReply{} + // Connect to nodes other than the last node in genesis.Validators, which is the node being tested. connectedIDs := set.NewSet[ids.NodeID](len(genesis.Validators) - 1) for _, vdr := range genesis.Validators[:len(genesis.Validators)-1] { connectedIDs.Add(vdr.NodeID) From f6bb38368b729cf4ba5ef17ec69b799ea8d27c1c Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 6 Aug 2024 11:20:38 +0300 Subject: [PATCH 014/199] use disconnect in stop tracking --- snow/uptime/manager.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/snow/uptime/manager.go b/snow/uptime/manager.go index 284fcdea7614..413a660ec172 100644 --- a/snow/uptime/manager.go +++ b/snow/uptime/manager.go @@ -90,9 +90,7 @@ func (m *manager) StopTracking(nodeIDs []ids.NodeID) error { // If the node is already connected, then we can just // update the uptime in the state and remove the connection if _, isConnected := m.connections[nodeID]; isConnected { - err := m.updateUptime(nodeID) - delete(m.connections, nodeID) - if err != nil { + if err := m.disconnect(nodeID); err != nil { return err } continue @@ -129,6 +127,10 @@ func (m *manager) IsConnected(nodeID ids.NodeID) bool { } func (m *manager) Disconnect(nodeID ids.NodeID) error { + return m.disconnect(nodeID) +} + +func (m *manager) disconnect(nodeID ids.NodeID) error { if err := m.updateUptime(nodeID); err != nil { return err } From 7a6f7eb8a44372c1f15341433b487e0b480a4f45 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 6 Aug 2024 12:14:41 +0300 Subject: [PATCH 015/199] remove todo comment --- vms/platformvm/service.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 501cb743fc6b..4fb48eddb941 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -832,9 +832,6 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato delegationFee := avajson.Float32(100 * float32(shares) / float32(reward.PercentDenominator)) var uptime *avajson.Float32 var connected *bool - // Only calculate uptime for primary network validators - // TODO: decide whether we want to keep connected for subnet validators - // it should be available at this point if args.SubnetID == constants.PrimaryNetworkID { rawUptime, err := s.vm.uptimeManager.CalculateUptimePercentFrom(currentStaker.NodeID, currentStaker.StartTime) if err != nil { From cba7a50cc4c4e77088d5e18eab3f0090dfd94cfd Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sun, 8 Sep 2024 16:33:16 +0300 Subject: [PATCH 016/199] remove unused err --- network/network.go | 1 - 1 file changed, 1 deletion(-) diff --git a/network/network.go b/network/network.go index 63f8c1f035b3..eab4ecca085e 100644 --- a/network/network.go +++ b/network/network.go @@ -52,7 +52,6 @@ var ( _ Network = (*network)(nil) errNotValidator = errors.New("node is not a validator") - errNotTracked = errors.New("subnet is not tracked") errExpectedProxy = errors.New("expected proxy") errExpectedTCPProtocol = errors.New("expected TCP protocol") errTrackingPrimaryNetwork = errors.New("cannot track primary network") From b4955d6f492929a85bdf3e40a45396a62aeecd99 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sun, 8 Sep 2024 16:36:53 +0300 Subject: [PATCH 017/199] remove subnet connector mock --- .../validatorsmock/subnet_connector.go | 55 ------------------- 1 file changed, 55 deletions(-) delete mode 100644 snow/validators/validatorsmock/subnet_connector.go diff --git a/snow/validators/validatorsmock/subnet_connector.go b/snow/validators/validatorsmock/subnet_connector.go deleted file mode 100644 index 118a287ac68a..000000000000 --- a/snow/validators/validatorsmock/subnet_connector.go +++ /dev/null @@ -1,55 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/snow/validators (interfaces: SubnetConnector) -// -// Generated by this command: -// -// mockgen -package=validatorsmock -destination=snow/validators/validatorsmock/subnet_connector.go -mock_names=SubnetConnector=SubnetConnector github.com/ava-labs/avalanchego/snow/validators SubnetConnector -// - -// Package validatorsmock is a generated GoMock package. -package validatorsmock - -import ( - context "context" - reflect "reflect" - - ids "github.com/ava-labs/avalanchego/ids" - gomock "go.uber.org/mock/gomock" -) - -// SubnetConnector is a mock of SubnetConnector interface. -type SubnetConnector struct { - ctrl *gomock.Controller - recorder *SubnetConnectorMockRecorder -} - -// SubnetConnectorMockRecorder is the mock recorder for SubnetConnector. -type SubnetConnectorMockRecorder struct { - mock *SubnetConnector -} - -// NewSubnetConnector creates a new mock instance. -func NewSubnetConnector(ctrl *gomock.Controller) *SubnetConnector { - mock := &SubnetConnector{ctrl: ctrl} - mock.recorder = &SubnetConnectorMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *SubnetConnector) EXPECT() *SubnetConnectorMockRecorder { - return m.recorder -} - -// ConnectedSubnet mocks base method. -func (m *SubnetConnector) ConnectedSubnet(arg0 context.Context, arg1 ids.NodeID, arg2 ids.ID) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ConnectedSubnet", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// ConnectedSubnet indicates an expected call of ConnectedSubnet. -func (mr *SubnetConnectorMockRecorder) ConnectedSubnet(arg0, arg1, arg2 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConnectedSubnet", reflect.TypeOf((*SubnetConnector)(nil).ConnectedSubnet), arg0, arg1, arg2) -} From 1b228421b98b6a2a7a6d21e8b58f721ce88115b2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 10 Sep 2024 14:43:39 -0400 Subject: [PATCH 018/199] WIP Add Expiry Replay Protection --- vms/platformvm/state/diff.go | 51 ++++++++++++ vms/platformvm/state/expiry.go | 116 ++++++++++++++++++++++++++++ vms/platformvm/state/expiry_test.go | 63 +++++++++++++++ vms/platformvm/state/mock_chain.go | 54 +++++++++++++ vms/platformvm/state/mock_diff.go | 54 +++++++++++++ vms/platformvm/state/mock_state.go | 54 +++++++++++++ vms/platformvm/state/state.go | 35 +++++++++ 7 files changed, 427 insertions(+) create mode 100644 vms/platformvm/state/expiry.go create mode 100644 vms/platformvm/state/expiry_test.go diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 16f7edf4435b..6f9e4fd0048d 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -41,6 +41,8 @@ type diff struct { // Subnet ID --> supply of native asset of the subnet currentSupply map[ids.ID]uint64 + expiryDiff *expiryDiff + currentStakerDiffs diffStakers // map of subnetID -> nodeID -> total accrued delegatee rewards modifiedDelegateeRewards map[ids.ID]map[ids.NodeID]uint64 @@ -77,6 +79,7 @@ func NewDiff( stateVersions: stateVersions, timestamp: parentState.GetTimestamp(), feeState: parentState.GetFeeState(), + expiryDiff: newExpiryDiff(), subnetOwners: make(map[ids.ID]fx.Owner), subnetManagers: make(map[ids.ID]chainIDAndAddr), }, nil @@ -136,6 +139,45 @@ func (d *diff) SetCurrentSupply(subnetID ids.ID, currentSupply uint64) { } } +func (d *diff) GetExpiryIterator() (iterator.Iterator[ExpiryEntry], error) { + parentState, ok := d.stateVersions.GetState(d.parentID) + if !ok { + return nil, fmt.Errorf("%w: %s", ErrMissingParentState, d.parentID) + } + + parentIterator, err := parentState.GetExpiryIterator() + if err != nil { + return nil, err + } + + return d.expiryDiff.getExpiryIterator(parentIterator), nil +} + +func (d *diff) HasExpiry(timestamp uint64, validationID ids.ID) (bool, error) { + entry := ExpiryEntry{ + Timestamp: timestamp, + ValidationID: validationID, + } + if has, modified := d.expiryDiff.hasExpiry(entry); modified { + return has, nil + } + + parentState, ok := d.stateVersions.GetState(d.parentID) + if !ok { + return false, fmt.Errorf("%w: %s", ErrMissingParentState, d.parentID) + } + + return parentState.HasExpiry(timestamp, validationID) +} + +func (d *diff) PutExpiry(timestamp uint64, validationID ids.ID) { + d.expiryDiff.PutExpiry(timestamp, validationID) +} + +func (d *diff) DeleteExpiry(timestamp uint64, validationID ids.ID) { + d.expiryDiff.DeleteExpiry(timestamp, validationID) +} + func (d *diff) GetCurrentValidator(subnetID ids.ID, nodeID ids.NodeID) (*Staker, error) { // If the validator was modified in this diff, return the modified // validator. @@ -440,6 +482,15 @@ func (d *diff) Apply(baseState Chain) error { for subnetID, supply := range d.currentSupply { baseState.SetCurrentSupply(subnetID, supply) } + addedExpiryIterator := iterator.FromTree(d.expiryDiff.added) + for addedExpiryIterator.Next() { + entry := addedExpiryIterator.Value() + baseState.PutExpiry(entry.Timestamp, entry.ValidationID) + } + addedExpiryIterator.Release() + for removed := range d.expiryDiff.removed { + baseState.DeleteExpiry(removed.Timestamp, removed.ValidationID) + } for _, subnetValidatorDiffs := range d.currentStakerDiffs.validatorDiffs { for _, validatorDiff := range subnetValidatorDiffs { switch validatorDiff.validatorStatus { diff --git a/vms/platformvm/state/expiry.go b/vms/platformvm/state/expiry.go new file mode 100644 index 000000000000..4faa59051331 --- /dev/null +++ b/vms/platformvm/state/expiry.go @@ -0,0 +1,116 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package state + +import ( + "encoding/binary" + "fmt" + + "github.com/google/btree" + + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/iterator" + "github.com/ava-labs/avalanchego/utils/set" +) + +// expiryKey = [timestamp] + [validationID] +const expiryKeyLength = database.Uint64Size + ids.IDLen + +var ( + errUnexpectedExpiryKeyLength = fmt.Errorf("expected expiry key length %d", expiryKeyLength) + + _ btree.LessFunc[ExpiryEntry] = ExpiryEntry.Less +) + +type Expiry interface { + GetExpiryIterator() (iterator.Iterator[ExpiryEntry], error) + HasExpiry(timestamp uint64, validationID ids.ID) (bool, error) + PutExpiry(timestamp uint64, validationID ids.ID) + DeleteExpiry(timestamp uint64, validationID ids.ID) +} + +type ExpiryEntry struct { + Timestamp uint64 + ValidationID ids.ID +} + +func (e *ExpiryEntry) Marshal() []byte { + key := make([]byte, expiryKeyLength) + binary.BigEndian.PutUint64(key, e.Timestamp) + copy(key[database.Uint64Size:], e.ValidationID[:]) + return key +} + +func (e *ExpiryEntry) Unmarshal(data []byte) error { + if len(data) != expiryKeyLength { + return errUnexpectedExpiryKeyLength + } + + e.Timestamp = binary.BigEndian.Uint64(data) + copy(e.ValidationID[:], data[database.Uint64Size:]) + return nil +} + +func (e ExpiryEntry) Less(o ExpiryEntry) bool { + switch { + case e.Timestamp < o.Timestamp: + return true + case e.Timestamp > o.Timestamp: + return false + default: + return e.ValidationID.Compare(o.ValidationID) == -1 + } +} + +type expiryDiff struct { + added *btree.BTreeG[ExpiryEntry] + removed set.Set[ExpiryEntry] +} + +func newExpiryDiff() *expiryDiff { + return &expiryDiff{ + added: btree.NewG(defaultTreeDegree, ExpiryEntry.Less), + } +} + +func (e *expiryDiff) PutExpiry(timestamp uint64, validationID ids.ID) { + entry := ExpiryEntry{ + Timestamp: timestamp, + ValidationID: validationID, + } + e.added.ReplaceOrInsert(entry) + e.removed.Remove(entry) +} + +func (e *expiryDiff) DeleteExpiry(timestamp uint64, validationID ids.ID) { + entry := ExpiryEntry{ + Timestamp: timestamp, + ValidationID: validationID, + } + e.added.Delete(entry) + e.removed.Add(entry) +} + +func (e *expiryDiff) getExpiryIterator(parentIterator iterator.Iterator[ExpiryEntry]) iterator.Iterator[ExpiryEntry] { + return iterator.Filter( + iterator.Merge( + ExpiryEntry.Less, + parentIterator, + iterator.FromTree(e.added), + ), + e.removed.Contains, + ) +} + +func (e *expiryDiff) hasExpiry(entry ExpiryEntry) (bool, bool) { + switch { + case e.removed.Contains(entry): + return false, true + case e.added.Has(entry): + return true, true + default: + return false, false + } +} diff --git a/vms/platformvm/state/expiry_test.go b/vms/platformvm/state/expiry_test.go new file mode 100644 index 000000000000..db35b5c0e0b3 --- /dev/null +++ b/vms/platformvm/state/expiry_test.go @@ -0,0 +1,63 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package state + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + "github.com/thepudds/fzgen/fuzzer" +) + +func FuzzMarshalExpiryKey(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + require := require.New(t) + + var entry ExpiryEntry + fz := fuzzer.NewFuzzer(data) + fz.Fill(&entry) + + marshalledData := entry.Marshal() + + var parsedEntry ExpiryEntry + err := parsedEntry.Unmarshal(marshalledData) + require.NoError(err) + require.Equal(entry, parsedEntry) + }) +} + +func FuzzMarshalExpiryKeyIteration(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ( + entry0 ExpiryEntry + entry1 ExpiryEntry + ) + fz := fuzzer.NewFuzzer(data) + fz.Fill(&entry0, &entry1) + + key0 := entry0.Marshal() + key1 := entry1.Marshal() + require.Equal( + t, + entry0.Less(entry1), + bytes.Compare(key0, key1) == -1, + ) + }) +} + +func FuzzUnmarshalExpiryKey(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + require := require.New(t) + + var entry ExpiryEntry + if err := entry.Unmarshal(data); err != nil { + require.ErrorIs(err, errUnexpectedExpiryKeyLength) + return + } + + marshalledData := entry.Marshal() + require.Equal(data, marshalledData) + }) +} diff --git a/vms/platformvm/state/mock_chain.go b/vms/platformvm/state/mock_chain.go index 727847a7c07f..2d86541eeb10 100644 --- a/vms/platformvm/state/mock_chain.go +++ b/vms/platformvm/state/mock_chain.go @@ -142,6 +142,18 @@ func (mr *MockChainMockRecorder) DeleteCurrentValidator(staker any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCurrentValidator", reflect.TypeOf((*MockChain)(nil).DeleteCurrentValidator), staker) } +// DeleteExpiry mocks base method. +func (m *MockChain) DeleteExpiry(timestamp uint64, validationID ids.ID) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "DeleteExpiry", timestamp, validationID) +} + +// DeleteExpiry indicates an expected call of DeleteExpiry. +func (mr *MockChainMockRecorder) DeleteExpiry(timestamp, validationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteExpiry", reflect.TypeOf((*MockChain)(nil).DeleteExpiry), timestamp, validationID) +} + // DeletePendingDelegator mocks base method. func (m *MockChain) DeletePendingDelegator(staker *Staker) { m.ctrl.T.Helper() @@ -253,6 +265,21 @@ func (mr *MockChainMockRecorder) GetDelegateeReward(subnetID, nodeID any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDelegateeReward", reflect.TypeOf((*MockChain)(nil).GetDelegateeReward), subnetID, nodeID) } +// GetExpiryIterator mocks base method. +func (m *MockChain) GetExpiryIterator() (iterator.Iterator[ExpiryEntry], error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetExpiryIterator") + ret0, _ := ret[0].(iterator.Iterator[ExpiryEntry]) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetExpiryIterator indicates an expected call of GetExpiryIterator. +func (mr *MockChainMockRecorder) GetExpiryIterator() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExpiryIterator", reflect.TypeOf((*MockChain)(nil).GetExpiryIterator)) +} + // GetFeeState mocks base method. func (m *MockChain) GetFeeState() gas.State { m.ctrl.T.Helper() @@ -403,6 +430,21 @@ func (mr *MockChainMockRecorder) GetUTXO(utxoID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUTXO", reflect.TypeOf((*MockChain)(nil).GetUTXO), utxoID) } +// HasExpiry mocks base method. +func (m *MockChain) HasExpiry(timestamp uint64, validationID ids.ID) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasExpiry", timestamp, validationID) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasExpiry indicates an expected call of HasExpiry. +func (mr *MockChainMockRecorder) HasExpiry(timestamp, validationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasExpiry", reflect.TypeOf((*MockChain)(nil).HasExpiry), timestamp, validationID) +} + // PutCurrentDelegator mocks base method. func (m *MockChain) PutCurrentDelegator(staker *Staker) { m.ctrl.T.Helper() @@ -429,6 +471,18 @@ func (mr *MockChainMockRecorder) PutCurrentValidator(staker any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutCurrentValidator", reflect.TypeOf((*MockChain)(nil).PutCurrentValidator), staker) } +// PutExpiry mocks base method. +func (m *MockChain) PutExpiry(timestamp uint64, validationID ids.ID) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "PutExpiry", timestamp, validationID) +} + +// PutExpiry indicates an expected call of PutExpiry. +func (mr *MockChainMockRecorder) PutExpiry(timestamp, validationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutExpiry", reflect.TypeOf((*MockChain)(nil).PutExpiry), timestamp, validationID) +} + // PutPendingDelegator mocks base method. func (m *MockChain) PutPendingDelegator(staker *Staker) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/mock_diff.go b/vms/platformvm/state/mock_diff.go index ccf6619b5b24..52b781649356 100644 --- a/vms/platformvm/state/mock_diff.go +++ b/vms/platformvm/state/mock_diff.go @@ -156,6 +156,18 @@ func (mr *MockDiffMockRecorder) DeleteCurrentValidator(staker any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCurrentValidator", reflect.TypeOf((*MockDiff)(nil).DeleteCurrentValidator), staker) } +// DeleteExpiry mocks base method. +func (m *MockDiff) DeleteExpiry(timestamp uint64, validationID ids.ID) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "DeleteExpiry", timestamp, validationID) +} + +// DeleteExpiry indicates an expected call of DeleteExpiry. +func (mr *MockDiffMockRecorder) DeleteExpiry(timestamp, validationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteExpiry", reflect.TypeOf((*MockDiff)(nil).DeleteExpiry), timestamp, validationID) +} + // DeletePendingDelegator mocks base method. func (m *MockDiff) DeletePendingDelegator(staker *Staker) { m.ctrl.T.Helper() @@ -267,6 +279,21 @@ func (mr *MockDiffMockRecorder) GetDelegateeReward(subnetID, nodeID any) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDelegateeReward", reflect.TypeOf((*MockDiff)(nil).GetDelegateeReward), subnetID, nodeID) } +// GetExpiryIterator mocks base method. +func (m *MockDiff) GetExpiryIterator() (iterator.Iterator[ExpiryEntry], error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetExpiryIterator") + ret0, _ := ret[0].(iterator.Iterator[ExpiryEntry]) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetExpiryIterator indicates an expected call of GetExpiryIterator. +func (mr *MockDiffMockRecorder) GetExpiryIterator() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExpiryIterator", reflect.TypeOf((*MockDiff)(nil).GetExpiryIterator)) +} + // GetFeeState mocks base method. func (m *MockDiff) GetFeeState() gas.State { m.ctrl.T.Helper() @@ -417,6 +444,21 @@ func (mr *MockDiffMockRecorder) GetUTXO(utxoID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUTXO", reflect.TypeOf((*MockDiff)(nil).GetUTXO), utxoID) } +// HasExpiry mocks base method. +func (m *MockDiff) HasExpiry(timestamp uint64, validationID ids.ID) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasExpiry", timestamp, validationID) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasExpiry indicates an expected call of HasExpiry. +func (mr *MockDiffMockRecorder) HasExpiry(timestamp, validationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasExpiry", reflect.TypeOf((*MockDiff)(nil).HasExpiry), timestamp, validationID) +} + // PutCurrentDelegator mocks base method. func (m *MockDiff) PutCurrentDelegator(staker *Staker) { m.ctrl.T.Helper() @@ -443,6 +485,18 @@ func (mr *MockDiffMockRecorder) PutCurrentValidator(staker any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutCurrentValidator", reflect.TypeOf((*MockDiff)(nil).PutCurrentValidator), staker) } +// PutExpiry mocks base method. +func (m *MockDiff) PutExpiry(timestamp uint64, validationID ids.ID) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "PutExpiry", timestamp, validationID) +} + +// PutExpiry indicates an expected call of PutExpiry. +func (mr *MockDiffMockRecorder) PutExpiry(timestamp, validationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutExpiry", reflect.TypeOf((*MockDiff)(nil).PutExpiry), timestamp, validationID) +} + // PutPendingDelegator mocks base method. func (m *MockDiff) PutPendingDelegator(staker *Staker) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index 527db5cf8a53..72503f3de408 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -257,6 +257,18 @@ func (mr *MockStateMockRecorder) DeleteCurrentValidator(staker any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCurrentValidator", reflect.TypeOf((*MockState)(nil).DeleteCurrentValidator), staker) } +// DeleteExpiry mocks base method. +func (m *MockState) DeleteExpiry(timestamp uint64, validationID ids.ID) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "DeleteExpiry", timestamp, validationID) +} + +// DeleteExpiry indicates an expected call of DeleteExpiry. +func (mr *MockStateMockRecorder) DeleteExpiry(timestamp, validationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteExpiry", reflect.TypeOf((*MockState)(nil).DeleteExpiry), timestamp, validationID) +} + // DeletePendingDelegator mocks base method. func (m *MockState) DeletePendingDelegator(staker *Staker) { m.ctrl.T.Helper() @@ -398,6 +410,21 @@ func (mr *MockStateMockRecorder) GetDelegateeReward(subnetID, nodeID any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDelegateeReward", reflect.TypeOf((*MockState)(nil).GetDelegateeReward), subnetID, nodeID) } +// GetExpiryIterator mocks base method. +func (m *MockState) GetExpiryIterator() (iterator.Iterator[ExpiryEntry], error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetExpiryIterator") + ret0, _ := ret[0].(iterator.Iterator[ExpiryEntry]) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetExpiryIterator indicates an expected call of GetExpiryIterator. +func (mr *MockStateMockRecorder) GetExpiryIterator() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExpiryIterator", reflect.TypeOf((*MockState)(nil).GetExpiryIterator)) +} + // GetFeeState mocks base method. func (m *MockState) GetFeeState() gas.State { m.ctrl.T.Helper() @@ -638,6 +665,21 @@ func (mr *MockStateMockRecorder) GetUptime(nodeID, subnetID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUptime", reflect.TypeOf((*MockState)(nil).GetUptime), nodeID, subnetID) } +// HasExpiry mocks base method. +func (m *MockState) HasExpiry(timestamp uint64, validationID ids.ID) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasExpiry", timestamp, validationID) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasExpiry indicates an expected call of HasExpiry. +func (mr *MockStateMockRecorder) HasExpiry(timestamp, validationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasExpiry", reflect.TypeOf((*MockState)(nil).HasExpiry), timestamp, validationID) +} + // PutCurrentDelegator mocks base method. func (m *MockState) PutCurrentDelegator(staker *Staker) { m.ctrl.T.Helper() @@ -664,6 +706,18 @@ func (mr *MockStateMockRecorder) PutCurrentValidator(staker any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutCurrentValidator", reflect.TypeOf((*MockState)(nil).PutCurrentValidator), staker) } +// PutExpiry mocks base method. +func (m *MockState) PutExpiry(timestamp uint64, validationID ids.ID) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "PutExpiry", timestamp, validationID) +} + +// PutExpiry indicates an expected call of PutExpiry. +func (mr *MockStateMockRecorder) PutExpiry(timestamp, validationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutExpiry", reflect.TypeOf((*MockState)(nil).PutExpiry), timestamp, validationID) +} + // PutPendingDelegator mocks base method. func (m *MockState) PutPendingDelegator(staker *Staker) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index b5288df75140..c7311a1ced34 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -82,6 +82,7 @@ var ( TransformedSubnetPrefix = []byte("transformedSubnet") SupplyPrefix = []byte("supply") ChainPrefix = []byte("chain") + ExpiryReplayProtectionPrefix = []byte("expiryReplayProtection") SingletonPrefix = []byte("singleton") TimestampKey = []byte("timestamp") @@ -96,6 +97,7 @@ var ( // Chain collects all methods to manage the state of the chain for block // execution. type Chain interface { + Expiry Stakers avax.UTXOAdder avax.UTXOGetter @@ -274,6 +276,8 @@ type stateBlk struct { * | '-. subnetID * | '-. list * | '-- txID -> nil + * |-. expiryReplayProtection + * | '-- timestamp + validationID -> nil * '-. singletons * |-- initializedKey -> nil * |-- blocksReindexedKey -> nil @@ -294,6 +298,9 @@ type state struct { baseDB *versiondb.Database + expiry *btree.BTreeG[ExpiryEntry] + expiryDiff *expiryDiff + currentStakers *baseStakers pendingStakers *baseStakers @@ -604,6 +611,9 @@ func New( blockCache: blockCache, blockDB: prefixdb.New(BlockPrefix, baseDB), + expiry: btree.NewG(defaultTreeDegree, ExpiryEntry.Less), + expiryDiff: newExpiryDiff(), + currentStakers: newBaseStakers(), pendingStakers: newBaseStakers(), @@ -678,6 +688,31 @@ func New( return s, nil } +func (s *state) GetExpiryIterator() (iterator.Iterator[ExpiryEntry], error) { + return s.expiryDiff.getExpiryIterator( + iterator.FromTree(s.expiry), + ), nil +} + +func (s *state) HasExpiry(timestamp uint64, validationID ids.ID) (bool, error) { + entry := ExpiryEntry{ + Timestamp: timestamp, + ValidationID: validationID, + } + if has, modified := s.expiryDiff.hasExpiry(entry); modified { + return has, nil + } + return s.expiry.Has(entry), nil +} + +func (s *state) PutExpiry(timestamp uint64, validationID ids.ID) { + s.expiryDiff.PutExpiry(timestamp, validationID) +} + +func (s *state) DeleteExpiry(timestamp uint64, validationID ids.ID) { + s.expiryDiff.DeleteExpiry(timestamp, validationID) +} + func (s *state) GetCurrentValidator(subnetID ids.ID, nodeID ids.NodeID) (*Staker, error) { return s.currentStakers.GetValidator(subnetID, nodeID) } From ad83805bc4daf1de74cd5c7b25e664947878b318 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 10 Sep 2024 14:49:05 -0400 Subject: [PATCH 019/199] rename --- vms/platformvm/state/expiry.go | 18 +++++++++--------- vms/platformvm/state/expiry_test.go | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/vms/platformvm/state/expiry.go b/vms/platformvm/state/expiry.go index 4faa59051331..2d27230c876e 100644 --- a/vms/platformvm/state/expiry.go +++ b/vms/platformvm/state/expiry.go @@ -15,11 +15,11 @@ import ( "github.com/ava-labs/avalanchego/utils/set" ) -// expiryKey = [timestamp] + [validationID] -const expiryKeyLength = database.Uint64Size + ids.IDLen +// expiryEntry = [timestamp] + [validationID] +const expiryEntryLength = database.Uint64Size + ids.IDLen var ( - errUnexpectedExpiryKeyLength = fmt.Errorf("expected expiry key length %d", expiryKeyLength) + errUnexpectedExpiryEntryLength = fmt.Errorf("expected expiry entry length %d", expiryEntryLength) _ btree.LessFunc[ExpiryEntry] = ExpiryEntry.Less ) @@ -37,15 +37,15 @@ type ExpiryEntry struct { } func (e *ExpiryEntry) Marshal() []byte { - key := make([]byte, expiryKeyLength) - binary.BigEndian.PutUint64(key, e.Timestamp) - copy(key[database.Uint64Size:], e.ValidationID[:]) - return key + data := make([]byte, expiryEntryLength) + binary.BigEndian.PutUint64(data, e.Timestamp) + copy(data[database.Uint64Size:], e.ValidationID[:]) + return data } func (e *ExpiryEntry) Unmarshal(data []byte) error { - if len(data) != expiryKeyLength { - return errUnexpectedExpiryKeyLength + if len(data) != expiryEntryLength { + return errUnexpectedExpiryEntryLength } e.Timestamp = binary.BigEndian.Uint64(data) diff --git a/vms/platformvm/state/expiry_test.go b/vms/platformvm/state/expiry_test.go index db35b5c0e0b3..49c1bb934ca4 100644 --- a/vms/platformvm/state/expiry_test.go +++ b/vms/platformvm/state/expiry_test.go @@ -11,7 +11,7 @@ import ( "github.com/thepudds/fzgen/fuzzer" ) -func FuzzMarshalExpiryKey(f *testing.F) { +func FuzzExpiryEntryMarshal(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { require := require.New(t) @@ -28,7 +28,7 @@ func FuzzMarshalExpiryKey(f *testing.F) { }) } -func FuzzMarshalExpiryKeyIteration(f *testing.F) { +func FuzzExpiryEntryMarshalOrdering(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { var ( entry0 ExpiryEntry @@ -47,13 +47,13 @@ func FuzzMarshalExpiryKeyIteration(f *testing.F) { }) } -func FuzzUnmarshalExpiryKey(f *testing.F) { +func FuzzExpiryEntryUnmarshal(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { require := require.New(t) var entry ExpiryEntry if err := entry.Unmarshal(data); err != nil { - require.ErrorIs(err, errUnexpectedExpiryKeyLength) + require.ErrorIs(err, errUnexpectedExpiryEntryLength) return } From 7820c1731b73818302b891afb222782a0b4aa4f4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 10 Sep 2024 15:02:23 -0400 Subject: [PATCH 020/199] wip --- vms/platformvm/state/diff.go | 20 ++++++++------------ vms/platformvm/state/expiry.go | 19 ++++++------------- vms/platformvm/state/expiry_test.go | 2 +- vms/platformvm/state/mock_chain.go | 24 ++++++++++++------------ vms/platformvm/state/mock_diff.go | 24 ++++++++++++------------ vms/platformvm/state/mock_state.go | 24 ++++++++++++------------ vms/platformvm/state/state.go | 14 +++++--------- 7 files changed, 56 insertions(+), 71 deletions(-) diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 6f9e4fd0048d..71993adedc00 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -153,11 +153,7 @@ func (d *diff) GetExpiryIterator() (iterator.Iterator[ExpiryEntry], error) { return d.expiryDiff.getExpiryIterator(parentIterator), nil } -func (d *diff) HasExpiry(timestamp uint64, validationID ids.ID) (bool, error) { - entry := ExpiryEntry{ - Timestamp: timestamp, - ValidationID: validationID, - } +func (d *diff) HasExpiry(entry ExpiryEntry) (bool, error) { if has, modified := d.expiryDiff.hasExpiry(entry); modified { return has, nil } @@ -167,15 +163,15 @@ func (d *diff) HasExpiry(timestamp uint64, validationID ids.ID) (bool, error) { return false, fmt.Errorf("%w: %s", ErrMissingParentState, d.parentID) } - return parentState.HasExpiry(timestamp, validationID) + return parentState.HasExpiry(entry) } -func (d *diff) PutExpiry(timestamp uint64, validationID ids.ID) { - d.expiryDiff.PutExpiry(timestamp, validationID) +func (d *diff) PutExpiry(entry ExpiryEntry) { + d.expiryDiff.PutExpiry(entry) } -func (d *diff) DeleteExpiry(timestamp uint64, validationID ids.ID) { - d.expiryDiff.DeleteExpiry(timestamp, validationID) +func (d *diff) DeleteExpiry(entry ExpiryEntry) { + d.expiryDiff.DeleteExpiry(entry) } func (d *diff) GetCurrentValidator(subnetID ids.ID, nodeID ids.NodeID) (*Staker, error) { @@ -485,11 +481,11 @@ func (d *diff) Apply(baseState Chain) error { addedExpiryIterator := iterator.FromTree(d.expiryDiff.added) for addedExpiryIterator.Next() { entry := addedExpiryIterator.Value() - baseState.PutExpiry(entry.Timestamp, entry.ValidationID) + baseState.PutExpiry(entry) } addedExpiryIterator.Release() for removed := range d.expiryDiff.removed { - baseState.DeleteExpiry(removed.Timestamp, removed.ValidationID) + baseState.DeleteExpiry(removed) } for _, subnetValidatorDiffs := range d.currentStakerDiffs.validatorDiffs { for _, validatorDiff := range subnetValidatorDiffs { diff --git a/vms/platformvm/state/expiry.go b/vms/platformvm/state/expiry.go index 2d27230c876e..dde2e9bbc91b 100644 --- a/vms/platformvm/state/expiry.go +++ b/vms/platformvm/state/expiry.go @@ -26,9 +26,9 @@ var ( type Expiry interface { GetExpiryIterator() (iterator.Iterator[ExpiryEntry], error) - HasExpiry(timestamp uint64, validationID ids.ID) (bool, error) - PutExpiry(timestamp uint64, validationID ids.ID) - DeleteExpiry(timestamp uint64, validationID ids.ID) + HasExpiry(ExpiryEntry) (bool, error) + PutExpiry(ExpiryEntry) + DeleteExpiry(ExpiryEntry) } type ExpiryEntry struct { @@ -53,6 +53,7 @@ func (e *ExpiryEntry) Unmarshal(data []byte) error { return nil } +// Invariant: Less produces the same ordering as the marshalled bytes. func (e ExpiryEntry) Less(o ExpiryEntry) bool { switch { case e.Timestamp < o.Timestamp: @@ -75,20 +76,12 @@ func newExpiryDiff() *expiryDiff { } } -func (e *expiryDiff) PutExpiry(timestamp uint64, validationID ids.ID) { - entry := ExpiryEntry{ - Timestamp: timestamp, - ValidationID: validationID, - } +func (e *expiryDiff) PutExpiry(entry ExpiryEntry) { e.added.ReplaceOrInsert(entry) e.removed.Remove(entry) } -func (e *expiryDiff) DeleteExpiry(timestamp uint64, validationID ids.ID) { - entry := ExpiryEntry{ - Timestamp: timestamp, - ValidationID: validationID, - } +func (e *expiryDiff) DeleteExpiry(entry ExpiryEntry) { e.added.Delete(entry) e.removed.Add(entry) } diff --git a/vms/platformvm/state/expiry_test.go b/vms/platformvm/state/expiry_test.go index 49c1bb934ca4..7fa82ab06aac 100644 --- a/vms/platformvm/state/expiry_test.go +++ b/vms/platformvm/state/expiry_test.go @@ -28,7 +28,7 @@ func FuzzExpiryEntryMarshal(f *testing.F) { }) } -func FuzzExpiryEntryMarshalOrdering(f *testing.F) { +func FuzzExpiryEntryLessAndMarshalOrdering(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { var ( entry0 ExpiryEntry diff --git a/vms/platformvm/state/mock_chain.go b/vms/platformvm/state/mock_chain.go index 2d86541eeb10..ac27471f3908 100644 --- a/vms/platformvm/state/mock_chain.go +++ b/vms/platformvm/state/mock_chain.go @@ -143,15 +143,15 @@ func (mr *MockChainMockRecorder) DeleteCurrentValidator(staker any) *gomock.Call } // DeleteExpiry mocks base method. -func (m *MockChain) DeleteExpiry(timestamp uint64, validationID ids.ID) { +func (m *MockChain) DeleteExpiry(arg0 ExpiryEntry) { m.ctrl.T.Helper() - m.ctrl.Call(m, "DeleteExpiry", timestamp, validationID) + m.ctrl.Call(m, "DeleteExpiry", arg0) } // DeleteExpiry indicates an expected call of DeleteExpiry. -func (mr *MockChainMockRecorder) DeleteExpiry(timestamp, validationID any) *gomock.Call { +func (mr *MockChainMockRecorder) DeleteExpiry(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteExpiry", reflect.TypeOf((*MockChain)(nil).DeleteExpiry), timestamp, validationID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteExpiry", reflect.TypeOf((*MockChain)(nil).DeleteExpiry), arg0) } // DeletePendingDelegator mocks base method. @@ -431,18 +431,18 @@ func (mr *MockChainMockRecorder) GetUTXO(utxoID any) *gomock.Call { } // HasExpiry mocks base method. -func (m *MockChain) HasExpiry(timestamp uint64, validationID ids.ID) (bool, error) { +func (m *MockChain) HasExpiry(arg0 ExpiryEntry) (bool, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HasExpiry", timestamp, validationID) + ret := m.ctrl.Call(m, "HasExpiry", arg0) ret0, _ := ret[0].(bool) ret1, _ := ret[1].(error) return ret0, ret1 } // HasExpiry indicates an expected call of HasExpiry. -func (mr *MockChainMockRecorder) HasExpiry(timestamp, validationID any) *gomock.Call { +func (mr *MockChainMockRecorder) HasExpiry(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasExpiry", reflect.TypeOf((*MockChain)(nil).HasExpiry), timestamp, validationID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasExpiry", reflect.TypeOf((*MockChain)(nil).HasExpiry), arg0) } // PutCurrentDelegator mocks base method. @@ -472,15 +472,15 @@ func (mr *MockChainMockRecorder) PutCurrentValidator(staker any) *gomock.Call { } // PutExpiry mocks base method. -func (m *MockChain) PutExpiry(timestamp uint64, validationID ids.ID) { +func (m *MockChain) PutExpiry(arg0 ExpiryEntry) { m.ctrl.T.Helper() - m.ctrl.Call(m, "PutExpiry", timestamp, validationID) + m.ctrl.Call(m, "PutExpiry", arg0) } // PutExpiry indicates an expected call of PutExpiry. -func (mr *MockChainMockRecorder) PutExpiry(timestamp, validationID any) *gomock.Call { +func (mr *MockChainMockRecorder) PutExpiry(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutExpiry", reflect.TypeOf((*MockChain)(nil).PutExpiry), timestamp, validationID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutExpiry", reflect.TypeOf((*MockChain)(nil).PutExpiry), arg0) } // PutPendingDelegator mocks base method. diff --git a/vms/platformvm/state/mock_diff.go b/vms/platformvm/state/mock_diff.go index 52b781649356..aaa21605f508 100644 --- a/vms/platformvm/state/mock_diff.go +++ b/vms/platformvm/state/mock_diff.go @@ -157,15 +157,15 @@ func (mr *MockDiffMockRecorder) DeleteCurrentValidator(staker any) *gomock.Call } // DeleteExpiry mocks base method. -func (m *MockDiff) DeleteExpiry(timestamp uint64, validationID ids.ID) { +func (m *MockDiff) DeleteExpiry(arg0 ExpiryEntry) { m.ctrl.T.Helper() - m.ctrl.Call(m, "DeleteExpiry", timestamp, validationID) + m.ctrl.Call(m, "DeleteExpiry", arg0) } // DeleteExpiry indicates an expected call of DeleteExpiry. -func (mr *MockDiffMockRecorder) DeleteExpiry(timestamp, validationID any) *gomock.Call { +func (mr *MockDiffMockRecorder) DeleteExpiry(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteExpiry", reflect.TypeOf((*MockDiff)(nil).DeleteExpiry), timestamp, validationID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteExpiry", reflect.TypeOf((*MockDiff)(nil).DeleteExpiry), arg0) } // DeletePendingDelegator mocks base method. @@ -445,18 +445,18 @@ func (mr *MockDiffMockRecorder) GetUTXO(utxoID any) *gomock.Call { } // HasExpiry mocks base method. -func (m *MockDiff) HasExpiry(timestamp uint64, validationID ids.ID) (bool, error) { +func (m *MockDiff) HasExpiry(arg0 ExpiryEntry) (bool, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HasExpiry", timestamp, validationID) + ret := m.ctrl.Call(m, "HasExpiry", arg0) ret0, _ := ret[0].(bool) ret1, _ := ret[1].(error) return ret0, ret1 } // HasExpiry indicates an expected call of HasExpiry. -func (mr *MockDiffMockRecorder) HasExpiry(timestamp, validationID any) *gomock.Call { +func (mr *MockDiffMockRecorder) HasExpiry(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasExpiry", reflect.TypeOf((*MockDiff)(nil).HasExpiry), timestamp, validationID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasExpiry", reflect.TypeOf((*MockDiff)(nil).HasExpiry), arg0) } // PutCurrentDelegator mocks base method. @@ -486,15 +486,15 @@ func (mr *MockDiffMockRecorder) PutCurrentValidator(staker any) *gomock.Call { } // PutExpiry mocks base method. -func (m *MockDiff) PutExpiry(timestamp uint64, validationID ids.ID) { +func (m *MockDiff) PutExpiry(arg0 ExpiryEntry) { m.ctrl.T.Helper() - m.ctrl.Call(m, "PutExpiry", timestamp, validationID) + m.ctrl.Call(m, "PutExpiry", arg0) } // PutExpiry indicates an expected call of PutExpiry. -func (mr *MockDiffMockRecorder) PutExpiry(timestamp, validationID any) *gomock.Call { +func (mr *MockDiffMockRecorder) PutExpiry(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutExpiry", reflect.TypeOf((*MockDiff)(nil).PutExpiry), timestamp, validationID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutExpiry", reflect.TypeOf((*MockDiff)(nil).PutExpiry), arg0) } // PutPendingDelegator mocks base method. diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index 72503f3de408..6758c4e064c6 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -258,15 +258,15 @@ func (mr *MockStateMockRecorder) DeleteCurrentValidator(staker any) *gomock.Call } // DeleteExpiry mocks base method. -func (m *MockState) DeleteExpiry(timestamp uint64, validationID ids.ID) { +func (m *MockState) DeleteExpiry(arg0 ExpiryEntry) { m.ctrl.T.Helper() - m.ctrl.Call(m, "DeleteExpiry", timestamp, validationID) + m.ctrl.Call(m, "DeleteExpiry", arg0) } // DeleteExpiry indicates an expected call of DeleteExpiry. -func (mr *MockStateMockRecorder) DeleteExpiry(timestamp, validationID any) *gomock.Call { +func (mr *MockStateMockRecorder) DeleteExpiry(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteExpiry", reflect.TypeOf((*MockState)(nil).DeleteExpiry), timestamp, validationID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteExpiry", reflect.TypeOf((*MockState)(nil).DeleteExpiry), arg0) } // DeletePendingDelegator mocks base method. @@ -666,18 +666,18 @@ func (mr *MockStateMockRecorder) GetUptime(nodeID, subnetID any) *gomock.Call { } // HasExpiry mocks base method. -func (m *MockState) HasExpiry(timestamp uint64, validationID ids.ID) (bool, error) { +func (m *MockState) HasExpiry(arg0 ExpiryEntry) (bool, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HasExpiry", timestamp, validationID) + ret := m.ctrl.Call(m, "HasExpiry", arg0) ret0, _ := ret[0].(bool) ret1, _ := ret[1].(error) return ret0, ret1 } // HasExpiry indicates an expected call of HasExpiry. -func (mr *MockStateMockRecorder) HasExpiry(timestamp, validationID any) *gomock.Call { +func (mr *MockStateMockRecorder) HasExpiry(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasExpiry", reflect.TypeOf((*MockState)(nil).HasExpiry), timestamp, validationID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasExpiry", reflect.TypeOf((*MockState)(nil).HasExpiry), arg0) } // PutCurrentDelegator mocks base method. @@ -707,15 +707,15 @@ func (mr *MockStateMockRecorder) PutCurrentValidator(staker any) *gomock.Call { } // PutExpiry mocks base method. -func (m *MockState) PutExpiry(timestamp uint64, validationID ids.ID) { +func (m *MockState) PutExpiry(arg0 ExpiryEntry) { m.ctrl.T.Helper() - m.ctrl.Call(m, "PutExpiry", timestamp, validationID) + m.ctrl.Call(m, "PutExpiry", arg0) } // PutExpiry indicates an expected call of PutExpiry. -func (mr *MockStateMockRecorder) PutExpiry(timestamp, validationID any) *gomock.Call { +func (mr *MockStateMockRecorder) PutExpiry(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutExpiry", reflect.TypeOf((*MockState)(nil).PutExpiry), timestamp, validationID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutExpiry", reflect.TypeOf((*MockState)(nil).PutExpiry), arg0) } // PutPendingDelegator mocks base method. diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index c7311a1ced34..523ed9e485d9 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -694,23 +694,19 @@ func (s *state) GetExpiryIterator() (iterator.Iterator[ExpiryEntry], error) { ), nil } -func (s *state) HasExpiry(timestamp uint64, validationID ids.ID) (bool, error) { - entry := ExpiryEntry{ - Timestamp: timestamp, - ValidationID: validationID, - } +func (s *state) HasExpiry(entry ExpiryEntry) (bool, error) { if has, modified := s.expiryDiff.hasExpiry(entry); modified { return has, nil } return s.expiry.Has(entry), nil } -func (s *state) PutExpiry(timestamp uint64, validationID ids.ID) { - s.expiryDiff.PutExpiry(timestamp, validationID) +func (s *state) PutExpiry(entry ExpiryEntry) { + s.expiryDiff.PutExpiry(entry) } -func (s *state) DeleteExpiry(timestamp uint64, validationID ids.ID) { - s.expiryDiff.DeleteExpiry(timestamp, validationID) +func (s *state) DeleteExpiry(entry ExpiryEntry) { + s.expiryDiff.DeleteExpiry(entry) } func (s *state) GetCurrentValidator(subnetID ids.ID, nodeID ids.NodeID) (*Staker, error) { From 20ed013f537852b7aa55b5d5e5efb8dbb845bb04 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 10 Sep 2024 15:24:57 -0400 Subject: [PATCH 021/199] Replace iterator equality helper --- utils/iterator/slice.go | 12 +++++ vms/platformvm/state/diff_test.go | 11 +++- vms/platformvm/state/stakers_test.go | 81 ++++++++++++++++------------ vms/platformvm/state/state_test.go | 13 +++-- 4 files changed, 79 insertions(+), 38 deletions(-) diff --git a/utils/iterator/slice.go b/utils/iterator/slice.go index d195a1adc14c..a7b18189aabc 100644 --- a/utils/iterator/slice.go +++ b/utils/iterator/slice.go @@ -5,6 +5,18 @@ package iterator var _ Iterator[any] = (*slice[any])(nil) +// ToSlice returns a slice that contains all of the elements from [it] in order. +// [it] will be released before returning. +func ToSlice[T any](it Iterator[T]) []T { + defer it.Release() + + var elements []T + for it.Next() { + elements = append(elements, it.Value()) + } + return elements +} + type slice[T any] struct { index int elements []T diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index a7eec42364b1..c4246b8aaeec 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/utils/iterator/iteratormock" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/gas" @@ -530,14 +531,20 @@ func assertChainsEqual(t *testing.T, expected, actual Chain) { actualCurrentStakerIterator, actualErr := actual.GetCurrentStakerIterator() require.Equal(expectedErr, actualErr) if expectedErr == nil { - assertIteratorsEqual(t, expectedCurrentStakerIterator, actualCurrentStakerIterator) + require.Equal( + iterator.ToSlice(expectedCurrentStakerIterator), + iterator.ToSlice(actualCurrentStakerIterator), + ) } expectedPendingStakerIterator, expectedErr := expected.GetPendingStakerIterator() actualPendingStakerIterator, actualErr := actual.GetPendingStakerIterator() require.Equal(expectedErr, actualErr) if expectedErr == nil { - assertIteratorsEqual(t, expectedPendingStakerIterator, actualPendingStakerIterator) + require.Equal( + iterator.ToSlice(expectedPendingStakerIterator), + iterator.ToSlice(actualPendingStakerIterator), + ) } require.Equal(expected.GetTimestamp(), actual.GetTimestamp()) diff --git a/vms/platformvm/state/stakers_test.go b/vms/platformvm/state/stakers_test.go index d536b2a719d8..8141959d80a4 100644 --- a/vms/platformvm/state/stakers_test.go +++ b/vms/platformvm/state/stakers_test.go @@ -86,7 +86,10 @@ func TestBaseStakersValidator(t *testing.T) { require.ErrorIs(err, database.ErrNotFound) stakerIterator := v.GetStakerIterator() - assertIteratorsEqual(t, iterator.FromSlice(delegator), stakerIterator) + require.Equal( + []*Staker{delegator}, + iterator.ToSlice(stakerIterator), + ) v.PutValidator(staker) @@ -97,7 +100,10 @@ func TestBaseStakersValidator(t *testing.T) { v.DeleteDelegator(delegator) stakerIterator = v.GetStakerIterator() - assertIteratorsEqual(t, iterator.FromSlice(staker), stakerIterator) + require.Equal( + []*Staker{staker}, + iterator.ToSlice(stakerIterator), + ) v.DeleteValidator(staker) @@ -105,30 +111,42 @@ func TestBaseStakersValidator(t *testing.T) { require.ErrorIs(err, database.ErrNotFound) stakerIterator = v.GetStakerIterator() - assertIteratorsEqual(t, iterator.Empty[*Staker]{}, stakerIterator) + require.Empty( + iterator.ToSlice(stakerIterator), + ) } func TestBaseStakersDelegator(t *testing.T) { + require := require.New(t) staker := newTestStaker() delegator := newTestStaker() v := newBaseStakers() delegatorIterator := v.GetDelegatorIterator(delegator.SubnetID, delegator.NodeID) - assertIteratorsEqual(t, iterator.Empty[*Staker]{}, delegatorIterator) + require.Empty( + iterator.ToSlice(delegatorIterator), + ) v.PutDelegator(delegator) delegatorIterator = v.GetDelegatorIterator(delegator.SubnetID, ids.GenerateTestNodeID()) - assertIteratorsEqual(t, iterator.Empty[*Staker]{}, delegatorIterator) + require.Empty( + iterator.ToSlice(delegatorIterator), + ) delegatorIterator = v.GetDelegatorIterator(delegator.SubnetID, delegator.NodeID) - assertIteratorsEqual(t, iterator.FromSlice(delegator), delegatorIterator) + require.Equal( + []*Staker{delegator}, + iterator.ToSlice(delegatorIterator), + ) v.DeleteDelegator(delegator) delegatorIterator = v.GetDelegatorIterator(delegator.SubnetID, delegator.NodeID) - assertIteratorsEqual(t, iterator.Empty[*Staker]{}, delegatorIterator) + require.Empty( + iterator.ToSlice(delegatorIterator), + ) v.PutValidator(staker) @@ -136,7 +154,9 @@ func TestBaseStakersDelegator(t *testing.T) { v.DeleteDelegator(delegator) delegatorIterator = v.GetDelegatorIterator(staker.SubnetID, staker.NodeID) - assertIteratorsEqual(t, iterator.Empty[*Staker]{}, delegatorIterator) + require.Empty( + iterator.ToSlice(delegatorIterator), + ) } func TestDiffStakersValidator(t *testing.T) { @@ -160,7 +180,10 @@ func TestDiffStakersValidator(t *testing.T) { require.Equal(unmodified, status) stakerIterator := v.GetStakerIterator(iterator.Empty[*Staker]{}) - assertIteratorsEqual(t, iterator.FromSlice(delegator), stakerIterator) + require.Equal( + []*Staker{delegator}, + iterator.ToSlice(stakerIterator), + ) require.NoError(v.PutValidator(staker)) @@ -177,7 +200,10 @@ func TestDiffStakersValidator(t *testing.T) { require.Equal(unmodified, status) stakerIterator = v.GetStakerIterator(iterator.Empty[*Staker]{}) - assertIteratorsEqual(t, iterator.FromSlice(delegator), stakerIterator) + require.Equal( + []*Staker{delegator}, + iterator.ToSlice(stakerIterator), + ) } func TestDiffStakersDeleteValidator(t *testing.T) { @@ -198,25 +224,33 @@ func TestDiffStakersDeleteValidator(t *testing.T) { } func TestDiffStakersDelegator(t *testing.T) { + require := require.New(t) staker := newTestStaker() delegator := newTestStaker() v := diffStakers{} - require.NoError(t, v.PutValidator(staker)) + require.NoError(v.PutValidator(staker)) delegatorIterator := v.GetDelegatorIterator(iterator.Empty[*Staker]{}, ids.GenerateTestID(), delegator.NodeID) - assertIteratorsEqual(t, iterator.Empty[*Staker]{}, delegatorIterator) + require.Empty( + iterator.ToSlice(delegatorIterator), + ) v.PutDelegator(delegator) delegatorIterator = v.GetDelegatorIterator(iterator.Empty[*Staker]{}, delegator.SubnetID, delegator.NodeID) - assertIteratorsEqual(t, iterator.FromSlice(delegator), delegatorIterator) + require.Equal( + []*Staker{delegator}, + iterator.ToSlice(delegatorIterator), + ) v.DeleteDelegator(delegator) delegatorIterator = v.GetDelegatorIterator(iterator.Empty[*Staker]{}, ids.GenerateTestID(), delegator.NodeID) - assertIteratorsEqual(t, iterator.Empty[*Staker]{}, delegatorIterator) + require.Empty( + iterator.ToSlice(delegatorIterator), + ) } func newTestStaker() *Staker { @@ -235,22 +269,3 @@ func newTestStaker() *Staker { Priority: txs.PrimaryNetworkDelegatorCurrentPriority, } } - -func assertIteratorsEqual(t *testing.T, expected, actual iterator.Iterator[*Staker]) { - require := require.New(t) - - t.Helper() - - for expected.Next() { - require.True(actual.Next()) - - expectedStaker := expected.Value() - actualStaker := actual.Value() - - require.Equal(expectedStaker, actualStaker) - } - require.False(actual.Next()) - - expected.Release() - actual.Release() -} diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 3be526333e3e..515794364d71 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -88,18 +88,25 @@ func TestStateSyncGenesis(t *testing.T) { delegatorIterator, err := state.GetCurrentDelegatorIterator(constants.PrimaryNetworkID, defaultValidatorNodeID) require.NoError(err) - assertIteratorsEqual(t, iterator.Empty[*Staker]{}, delegatorIterator) + require.Empty( + iterator.ToSlice(delegatorIterator), + ) stakerIterator, err := state.GetCurrentStakerIterator() require.NoError(err) - assertIteratorsEqual(t, iterator.FromSlice(staker), stakerIterator) + require.Equal( + []*Staker{staker}, + iterator.ToSlice(stakerIterator), + ) _, err = state.GetPendingValidator(constants.PrimaryNetworkID, defaultValidatorNodeID) require.ErrorIs(err, database.ErrNotFound) delegatorIterator, err = state.GetPendingDelegatorIterator(constants.PrimaryNetworkID, defaultValidatorNodeID) require.NoError(err) - assertIteratorsEqual(t, iterator.Empty[*Staker]{}, delegatorIterator) + require.Empty( + iterator.ToSlice(delegatorIterator), + ) } // Whenever we store a staker, a whole bunch a data structures are updated From a02b51ccc6070d1944bd9dc6aad22f157b1c5ae5 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 10 Sep 2024 22:23:06 -0400 Subject: [PATCH 022/199] Finish diff implementation --- utils/iterator/deduplicate.go | 40 ++++++++ utils/iterator/deduplicate_test.go | 18 ++++ vms/platformvm/state/diff_test.go | 159 +++++++++++++++++++++++++++++ vms/platformvm/state/expiry.go | 16 +-- 4 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 utils/iterator/deduplicate.go create mode 100644 utils/iterator/deduplicate_test.go diff --git a/utils/iterator/deduplicate.go b/utils/iterator/deduplicate.go new file mode 100644 index 000000000000..b36c1e0a1102 --- /dev/null +++ b/utils/iterator/deduplicate.go @@ -0,0 +1,40 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package iterator + +import "github.com/ava-labs/avalanchego/utils/set" + +var _ Iterator[any] = (*deduplicator[any])(nil) + +type deduplicator[T comparable] struct { + it Iterator[T] + seen set.Set[T] +} + +// Deduplicate returns an iterator that skips the elements that have already +// been returned from [it]. +func Deduplicate[T comparable](it Iterator[T]) Iterator[T] { + return &deduplicator[T]{ + it: it, + } +} + +func (i *deduplicator[_]) Next() bool { + for i.it.Next() { + element := i.it.Value() + if !i.seen.Contains(element) { + i.seen.Add(element) + return true + } + } + return false +} + +func (i *deduplicator[T]) Value() T { + return i.it.Value() +} + +func (i *deduplicator[_]) Release() { + i.it.Release() +} diff --git a/utils/iterator/deduplicate_test.go b/utils/iterator/deduplicate_test.go new file mode 100644 index 000000000000..291d186df574 --- /dev/null +++ b/utils/iterator/deduplicate_test.go @@ -0,0 +1,18 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package iterator + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDeduplicate(t *testing.T) { + require.Equal( + t, + []int{0, 1, 2, 3}, + ToSlice(Deduplicate(FromSlice(0, 1, 2, 1, 2, 0, 3))), + ) +} diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index c4246b8aaeec..b0b2247a1504 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -17,6 +17,7 @@ import ( "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/utils/iterator/iteratormock" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/fx/fxmock" @@ -112,6 +113,154 @@ func TestDiffCurrentSupply(t *testing.T) { assertChainsEqual(t, state, d) } +func TestDiffExpiry(t *testing.T) { + type op struct { + put bool + entry ExpiryEntry + } + tests := []struct { + name string + initialExpiries []ExpiryEntry + ops []op + expectedExpiries []ExpiryEntry + }{ + { + name: "empty noop", + }, + { + name: "insert", + ops: []op{ + { + put: true, + entry: ExpiryEntry{Timestamp: 1}, + }, + }, + expectedExpiries: []ExpiryEntry{ + {Timestamp: 1}, + }, + }, + { + name: "remove", + initialExpiries: []ExpiryEntry{ + {Timestamp: 1}, + }, + ops: []op{ + { + put: false, + entry: ExpiryEntry{Timestamp: 1}, + }, + }, + }, + { + name: "add and immediately remove", + ops: []op{ + { + put: true, + entry: ExpiryEntry{Timestamp: 1}, + }, + { + put: false, + entry: ExpiryEntry{Timestamp: 1}, + }, + }, + }, + { + name: "add + remove + add", + ops: []op{ + { + put: true, + entry: ExpiryEntry{Timestamp: 1}, + }, + { + put: false, + entry: ExpiryEntry{Timestamp: 1}, + }, + { + put: true, + entry: ExpiryEntry{Timestamp: 1}, + }, + }, + expectedExpiries: []ExpiryEntry{ + {Timestamp: 1}, + }, + }, + { + name: "everything", + initialExpiries: []ExpiryEntry{ + {Timestamp: 1}, + {Timestamp: 2}, + {Timestamp: 3}, + }, + ops: []op{ + { + put: false, + entry: ExpiryEntry{Timestamp: 1}, + }, + { + put: false, + entry: ExpiryEntry{Timestamp: 2}, + }, + { + put: true, + entry: ExpiryEntry{Timestamp: 1}, + }, + }, + expectedExpiries: []ExpiryEntry{ + {Timestamp: 1}, + {Timestamp: 3}, + }, + }, + } + + for _, test := range tests { + require := require.New(t) + + state := newTestState(t, memdb.New()) + for _, expiry := range test.initialExpiries { + state.PutExpiry(expiry) + } + + d, err := NewDiffOn(state) + require.NoError(err) + + otherExpiries := set.Of(test.initialExpiries...) + for _, op := range test.ops { + if op.put { + d.PutExpiry(op.entry) + } else { + d.DeleteExpiry(op.entry) + } + otherExpiries.Add(op.entry) + } + otherExpiries.Remove(test.expectedExpiries...) + + verifyChain := func(chain Chain) { + expiryIterator, err := chain.GetExpiryIterator() + require.NoError(err) + require.Equal( + test.expectedExpiries, + iterator.ToSlice(expiryIterator), + ) + + for _, expiry := range test.expectedExpiries { + has, err := chain.HasExpiry(expiry) + require.NoError(err) + require.True(has) + } + for expiry := range otherExpiries { + has, err := chain.HasExpiry(expiry) + require.NoError(err) + require.False(has) + } + } + + verifyChain(d) + require.NoError(d.Apply(state)) + verifyChain(state) + assertChainsEqual(t, d, state) + } +} + func TestDiffCurrentValidator(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) @@ -527,6 +676,16 @@ func assertChainsEqual(t *testing.T, expected, actual Chain) { t.Helper() + expectedExpiryIterator, expectedErr := expected.GetExpiryIterator() + actualExpiryIterator, actualErr := actual.GetExpiryIterator() + require.Equal(expectedErr, actualErr) + if expectedErr == nil { + require.Equal( + iterator.ToSlice(expectedExpiryIterator), + iterator.ToSlice(actualExpiryIterator), + ) + } + expectedCurrentStakerIterator, expectedErr := expected.GetCurrentStakerIterator() actualCurrentStakerIterator, actualErr := actual.GetCurrentStakerIterator() require.Equal(expectedErr, actualErr) diff --git a/vms/platformvm/state/expiry.go b/vms/platformvm/state/expiry.go index dde2e9bbc91b..9bd0139267e5 100644 --- a/vms/platformvm/state/expiry.go +++ b/vms/platformvm/state/expiry.go @@ -87,13 +87,17 @@ func (e *expiryDiff) DeleteExpiry(entry ExpiryEntry) { } func (e *expiryDiff) getExpiryIterator(parentIterator iterator.Iterator[ExpiryEntry]) iterator.Iterator[ExpiryEntry] { - return iterator.Filter( - iterator.Merge( - ExpiryEntry.Less, - parentIterator, - iterator.FromTree(e.added), + // The iterators are deduplicated so that additions that were present in the + // parent iterator are not duplicated. + return iterator.Deduplicate( + iterator.Filter( + iterator.Merge( + ExpiryEntry.Less, + parentIterator, + iterator.FromTree(e.added), + ), + e.removed.Contains, ), - e.removed.Contains, ) } From 7bda9b1e58b6f1ecf273cc4c264379e6b580dd2c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 10 Sep 2024 23:01:10 -0400 Subject: [PATCH 023/199] Finish state implementation --- vms/platformvm/state/state.go | 48 ++++++++++++++++++++++++++++++ vms/platformvm/state/state_test.go | 32 ++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 87e63bd9b9b2..877f6c2b1208 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -305,6 +305,7 @@ type state struct { expiry *btree.BTreeG[ExpiryEntry] expiryDiff *expiryDiff + expiryDB database.Database currentStakers *baseStakers pendingStakers *baseStakers @@ -619,6 +620,7 @@ func New( expiry: btree.NewG(defaultTreeDegree, ExpiryEntry.Less), expiryDiff: newExpiryDiff(), + expiryDB: prefixdb.New(ExpiryReplayProtectionPrefix, baseDB), currentStakers: newBaseStakers(), pendingStakers: newBaseStakers(), @@ -1367,6 +1369,7 @@ func (s *state) syncGenesis(genesisBlk block.Block, genesis *genesis.Genesis) er func (s *state) load() error { return errors.Join( s.loadMetadata(), + s.loadExpiry(), s.loadCurrentValidators(), s.loadPendingValidators(), s.initValidatorSets(), @@ -1438,6 +1441,23 @@ func (s *state) loadMetadata() error { return nil } +func (s *state) loadExpiry() error { + it := s.expiryDB.NewIterator() + defer it.Release() + + for it.Next() { + key := it.Key() + + var entry ExpiryEntry + if err := entry.Unmarshal(key); err != nil { + return err + } + s.expiry.ReplaceOrInsert(entry) + } + + return nil +} + func (s *state) loadCurrentValidators() error { s.currentStakers = newBaseStakers() @@ -1736,6 +1756,7 @@ func (s *state) write(updateValidators bool, height uint64) error { return errors.Join( s.writeBlocks(), + s.writeExpiry(), s.writeCurrentStakers(updateValidators, height, codecVersion), s.writePendingStakers(), s.WriteValidatorMetadata(s.currentValidatorList, s.currentSubnetValidatorList, codecVersion), // Must be called after writeCurrentStakers @@ -1754,6 +1775,7 @@ func (s *state) write(updateValidators bool, height uint64) error { func (s *state) Close() error { return errors.Join( + s.expiryDB.Close(), s.pendingSubnetValidatorBaseDB.Close(), s.pendingSubnetDelegatorBaseDB.Close(), s.pendingDelegatorBaseDB.Close(), @@ -1960,6 +1982,32 @@ func (s *state) GetBlockIDAtHeight(height uint64) (ids.ID, error) { return blkID, nil } +func (s *state) writeExpiry() error { + it := iterator.FromTree(s.expiryDiff.added) + defer it.Release() + + for it.Next() { + entry := it.Value() + s.expiry.ReplaceOrInsert(entry) + + key := entry.Marshal() + if err := s.expiryDB.Put(key, nil); err != nil { + return err + } + } + for removed := range s.expiryDiff.removed { + s.expiry.Delete(removed) + + key := removed.Marshal() + if err := s.expiryDB.Delete(key); err != nil { + return err + } + } + + s.expiryDiff = newExpiryDiff() + return nil +} + func (s *state) writeCurrentStakers(updateValidators bool, height uint64, codecVersion uint16) error { for subnetID, validatorDiffs := range s.currentStakers.validatorDiffs { delete(s.currentStakers.validatorDiffs, subnetID) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 515794364d71..a0dcd0cb7dcc 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1577,3 +1577,35 @@ func TestGetFeeStateErrors(t *testing.T) { }) } } + +// Verify that committing the state writes the expiry changes to the database +// and that loading the state fetches the expiry from the database. +func TestStateExpiryCommitAndLoad(t *testing.T) { + require := require.New(t) + + db := memdb.New() + s := newTestState(t, db) + + // Populate an entry. + expiry := ExpiryEntry{ + Timestamp: 1, + } + s.PutExpiry(expiry) + require.NoError(s.Commit()) + + // Verify that the entry was written and loaded correctly. + s = newTestState(t, db) + has, err := s.HasExpiry(expiry) + require.NoError(err) + require.True(has) + + // Delete an entry. + s.DeleteExpiry(expiry) + require.NoError(s.Commit()) + + // Verify that the entry was deleted correctly. + s = newTestState(t, db) + has, err = s.HasExpiry(expiry) + require.NoError(err) + require.False(has) +} From f24ca9f8d27fbc7ef8d01c701d0ab7302d5618db Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 10 Sep 2024 23:39:09 -0400 Subject: [PATCH 024/199] lint --- vms/platformvm/state/expiry_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vms/platformvm/state/expiry_test.go b/vms/platformvm/state/expiry_test.go index 7fa82ab06aac..38a0d07f9ab0 100644 --- a/vms/platformvm/state/expiry_test.go +++ b/vms/platformvm/state/expiry_test.go @@ -22,8 +22,7 @@ func FuzzExpiryEntryMarshal(f *testing.F) { marshalledData := entry.Marshal() var parsedEntry ExpiryEntry - err := parsedEntry.Unmarshal(marshalledData) - require.NoError(err) + require.NoError(parsedEntry.Unmarshal(marshalledData)) require.Equal(entry, parsedEntry) }) } From 463ce028a92e78f9b448b1dd26ccc3fc9ae68b51 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 10 Sep 2024 23:45:11 -0400 Subject: [PATCH 025/199] Implement iterator deduplicator --- utils/iterator/deduplicate.go | 40 ++++++++++++++++++++++++++++++ utils/iterator/deduplicate_test.go | 18 ++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 utils/iterator/deduplicate.go create mode 100644 utils/iterator/deduplicate_test.go diff --git a/utils/iterator/deduplicate.go b/utils/iterator/deduplicate.go new file mode 100644 index 000000000000..b36c1e0a1102 --- /dev/null +++ b/utils/iterator/deduplicate.go @@ -0,0 +1,40 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package iterator + +import "github.com/ava-labs/avalanchego/utils/set" + +var _ Iterator[any] = (*deduplicator[any])(nil) + +type deduplicator[T comparable] struct { + it Iterator[T] + seen set.Set[T] +} + +// Deduplicate returns an iterator that skips the elements that have already +// been returned from [it]. +func Deduplicate[T comparable](it Iterator[T]) Iterator[T] { + return &deduplicator[T]{ + it: it, + } +} + +func (i *deduplicator[_]) Next() bool { + for i.it.Next() { + element := i.it.Value() + if !i.seen.Contains(element) { + i.seen.Add(element) + return true + } + } + return false +} + +func (i *deduplicator[T]) Value() T { + return i.it.Value() +} + +func (i *deduplicator[_]) Release() { + i.it.Release() +} diff --git a/utils/iterator/deduplicate_test.go b/utils/iterator/deduplicate_test.go new file mode 100644 index 000000000000..291d186df574 --- /dev/null +++ b/utils/iterator/deduplicate_test.go @@ -0,0 +1,18 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package iterator + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDeduplicate(t *testing.T) { + require.Equal( + t, + []int{0, 1, 2, 3}, + ToSlice(Deduplicate(FromSlice(0, 1, 2, 1, 2, 0, 3))), + ) +} From 3cc413a1b63753b38cacdc1411a1e552d87afa52 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 11 Sep 2024 11:34:40 -0400 Subject: [PATCH 026/199] Reuse Filter --- utils/iterator/deduplicate.go | 40 ------------------------------ utils/iterator/deduplicate_test.go | 18 -------------- utils/iterator/filter.go | 15 +++++++++++ utils/iterator/filter_test.go | 15 ++++++++--- 4 files changed, 27 insertions(+), 61 deletions(-) delete mode 100644 utils/iterator/deduplicate.go delete mode 100644 utils/iterator/deduplicate_test.go diff --git a/utils/iterator/deduplicate.go b/utils/iterator/deduplicate.go deleted file mode 100644 index b36c1e0a1102..000000000000 --- a/utils/iterator/deduplicate.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package iterator - -import "github.com/ava-labs/avalanchego/utils/set" - -var _ Iterator[any] = (*deduplicator[any])(nil) - -type deduplicator[T comparable] struct { - it Iterator[T] - seen set.Set[T] -} - -// Deduplicate returns an iterator that skips the elements that have already -// been returned from [it]. -func Deduplicate[T comparable](it Iterator[T]) Iterator[T] { - return &deduplicator[T]{ - it: it, - } -} - -func (i *deduplicator[_]) Next() bool { - for i.it.Next() { - element := i.it.Value() - if !i.seen.Contains(element) { - i.seen.Add(element) - return true - } - } - return false -} - -func (i *deduplicator[T]) Value() T { - return i.it.Value() -} - -func (i *deduplicator[_]) Release() { - i.it.Release() -} diff --git a/utils/iterator/deduplicate_test.go b/utils/iterator/deduplicate_test.go deleted file mode 100644 index 291d186df574..000000000000 --- a/utils/iterator/deduplicate_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package iterator - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestDeduplicate(t *testing.T) { - require.Equal( - t, - []int{0, 1, 2, 3}, - ToSlice(Deduplicate(FromSlice(0, 1, 2, 1, 2, 0, 3))), - ) -} diff --git a/utils/iterator/filter.go b/utils/iterator/filter.go index e8a11464457d..f26b082aeab2 100644 --- a/utils/iterator/filter.go +++ b/utils/iterator/filter.go @@ -3,6 +3,8 @@ package iterator +import "github.com/ava-labs/avalanchego/utils/set" + var _ Iterator[any] = (*filtered[any])(nil) type filtered[T any] struct { @@ -19,6 +21,19 @@ func Filter[T any](it Iterator[T], filter func(T) bool) Iterator[T] { } } +// Deduplicate returns an iterator that skips the elements that have already +// been returned from [it]. +func Deduplicate[T comparable](it Iterator[T]) Iterator[T] { + var seen set.Set[T] + return Filter(it, func(e T) bool { + if seen.Contains(e) { + return true + } + seen.Add(e) + return false + }) +} + func (i *filtered[_]) Next() bool { for i.it.Next() { element := i.it.Value() diff --git a/utils/iterator/filter_test.go b/utils/iterator/filter_test.go index bf523017fe92..56c47892095e 100644 --- a/utils/iterator/filter_test.go +++ b/utils/iterator/filter_test.go @@ -10,8 +10,9 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/vms/platformvm/state" + + . "github.com/ava-labs/avalanchego/utils/iterator" ) func TestFilter(t *testing.T) { @@ -40,8 +41,8 @@ func TestFilter(t *testing.T) { stakers[3].TxID: stakers[3], } - it := iterator.Filter( - iterator.FromSlice(stakers[:3]...), + it := Filter( + FromSlice(stakers[:3]...), func(staker *state.Staker) bool { _, ok := maskedStakers[staker.TxID] return ok @@ -55,3 +56,11 @@ func TestFilter(t *testing.T) { it.Release() require.False(it.Next()) } + +func TestDeduplicate(t *testing.T) { + require.Equal( + t, + []int{0, 1, 2, 3}, + ToSlice(Deduplicate(FromSlice(0, 1, 2, 1, 2, 0, 3))), + ) +} From 114beeb53adfb00e629f60e6676e28e225aae350 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 11 Sep 2024 12:05:55 -0400 Subject: [PATCH 027/199] nit --- vms/platformvm/state/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 877f6c2b1208..61991e7ac6db 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1450,7 +1450,7 @@ func (s *state) loadExpiry() error { var entry ExpiryEntry if err := entry.Unmarshal(key); err != nil { - return err + return fmt.Errorf("failed to unmarshal ExpiryEntry during load: %w", err) } s.expiry.ReplaceOrInsert(entry) } From 57810a00dda1bd421a5449e1761844f7696b06b2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 11 Sep 2024 19:09:49 -0400 Subject: [PATCH 028/199] Add comments --- vms/platformvm/state/expiry.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vms/platformvm/state/expiry.go b/vms/platformvm/state/expiry.go index 9bd0139267e5..5a00297c439e 100644 --- a/vms/platformvm/state/expiry.go +++ b/vms/platformvm/state/expiry.go @@ -25,9 +25,19 @@ var ( ) type Expiry interface { + // GetExpiryIterator returns an iterator of all the expiry entries in order + // of lowest to highest timestamp. GetExpiryIterator() (iterator.Iterator[ExpiryEntry], error) + + // HasExpiry returns true if the database has the specified entry. HasExpiry(ExpiryEntry) (bool, error) + + // PutExpiry adds the entry to the database. If the entry already exists, it + // is a noop. PutExpiry(ExpiryEntry) + + // DeleteExpiry removes the entry from the database. If the entry doesn't + // exist, it is a noop. DeleteExpiry(ExpiryEntry) } From e89671a15e6f1b8a27397836689d27b6d6149b65 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 12 Sep 2024 11:38:43 -0400 Subject: [PATCH 029/199] otherExpiries -> unexpectedExpiries --- vms/platformvm/state/diff_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index b0b2247a1504..0c763559157d 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -223,16 +223,16 @@ func TestDiffExpiry(t *testing.T) { d, err := NewDiffOn(state) require.NoError(err) - otherExpiries := set.Of(test.initialExpiries...) + unexpectedExpiries := set.Of(test.initialExpiries...) for _, op := range test.ops { if op.put { d.PutExpiry(op.entry) } else { d.DeleteExpiry(op.entry) } - otherExpiries.Add(op.entry) + unexpectedExpiries.Add(op.entry) } - otherExpiries.Remove(test.expectedExpiries...) + unexpectedExpiries.Remove(test.expectedExpiries...) verifyChain := func(chain Chain) { expiryIterator, err := chain.GetExpiryIterator() @@ -247,7 +247,7 @@ func TestDiffExpiry(t *testing.T) { require.NoError(err) require.True(has) } - for expiry := range otherExpiries { + for expiry := range unexpectedExpiries { has, err := chain.HasExpiry(expiry) require.NoError(err) require.False(has) From 72f972c3ece6a6a017b8fb65468ebd371bd3e66e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 12 Sep 2024 12:00:30 -0400 Subject: [PATCH 030/199] restructure test --- vms/platformvm/state/diff_test.go | 40 ++++++++++++++++--------------- vms/platformvm/state/expiry.go | 14 +++++++---- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index 0c763559157d..b71599928e23 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -119,10 +119,9 @@ func TestDiffExpiry(t *testing.T) { entry ExpiryEntry } tests := []struct { - name string - initialExpiries []ExpiryEntry - ops []op - expectedExpiries []ExpiryEntry + name string + initialExpiries []ExpiryEntry + ops []op }{ { name: "empty noop", @@ -135,9 +134,6 @@ func TestDiffExpiry(t *testing.T) { entry: ExpiryEntry{Timestamp: 1}, }, }, - expectedExpiries: []ExpiryEntry{ - {Timestamp: 1}, - }, }, { name: "remove", @@ -180,9 +176,6 @@ func TestDiffExpiry(t *testing.T) { entry: ExpiryEntry{Timestamp: 1}, }, }, - expectedExpiries: []ExpiryEntry{ - {Timestamp: 1}, - }, }, { name: "everything", @@ -205,10 +198,6 @@ func TestDiffExpiry(t *testing.T) { entry: ExpiryEntry{Timestamp: 1}, }, }, - expectedExpiries: []ExpiryEntry{ - {Timestamp: 1}, - {Timestamp: 3}, - }, }, } @@ -223,26 +212,39 @@ func TestDiffExpiry(t *testing.T) { d, err := NewDiffOn(state) require.NoError(err) - unexpectedExpiries := set.Of(test.initialExpiries...) + var ( + expectedExpiries = set.Of(test.initialExpiries...) + unexpectedExpiries set.Set[ExpiryEntry] + ) for _, op := range test.ops { if op.put { d.PutExpiry(op.entry) + expectedExpiries.Add(op.entry) + unexpectedExpiries.Remove(op.entry) } else { d.DeleteExpiry(op.entry) + expectedExpiries.Remove(op.entry) + unexpectedExpiries.Add(op.entry) } - unexpectedExpiries.Add(op.entry) } - unexpectedExpiries.Remove(test.expectedExpiries...) + + // If expectedExpiries is empty, we want expectedExpiriesSlice to be + // nil. + var expectedExpiriesSlice []ExpiryEntry + if expectedExpiries.Len() > 0 { + expectedExpiriesSlice = expectedExpiries.List() + utils.Sort(expectedExpiriesSlice) + } verifyChain := func(chain Chain) { expiryIterator, err := chain.GetExpiryIterator() require.NoError(err) require.Equal( - test.expectedExpiries, + expectedExpiriesSlice, iterator.ToSlice(expiryIterator), ) - for _, expiry := range test.expectedExpiries { + for expiry := range expectedExpiries { has, err := chain.HasExpiry(expiry) require.NoError(err) require.True(has) diff --git a/vms/platformvm/state/expiry.go b/vms/platformvm/state/expiry.go index 5a00297c439e..ccd37f17db4c 100644 --- a/vms/platformvm/state/expiry.go +++ b/vms/platformvm/state/expiry.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/utils/set" ) @@ -22,6 +23,7 @@ var ( errUnexpectedExpiryEntryLength = fmt.Errorf("expected expiry entry length %d", expiryEntryLength) _ btree.LessFunc[ExpiryEntry] = ExpiryEntry.Less + _ utils.Sortable[ExpiryEntry] = ExpiryEntry{} ) type Expiry interface { @@ -63,15 +65,19 @@ func (e *ExpiryEntry) Unmarshal(data []byte) error { return nil } -// Invariant: Less produces the same ordering as the marshalled bytes. func (e ExpiryEntry) Less(o ExpiryEntry) bool { + return e.Compare(o) == -1 +} + +// Invariant: Compare produces the same ordering as the marshalled bytes. +func (e ExpiryEntry) Compare(o ExpiryEntry) int { switch { case e.Timestamp < o.Timestamp: - return true + return -1 case e.Timestamp > o.Timestamp: - return false + return 1 default: - return e.ValidationID.Compare(o.ValidationID) == -1 + return e.ValidationID.Compare(o.ValidationID) } } From e8eea6facc0c944ca87d821b1245f7172943f446 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 12 Sep 2024 16:51:04 -0400 Subject: [PATCH 031/199] reduce diff --- vms/platformvm/state/diff.go | 16 ++++++------- vms/platformvm/state/expiry.go | 41 +++++++++++----------------------- vms/platformvm/state/state.go | 27 +++++++++------------- 3 files changed, 31 insertions(+), 53 deletions(-) diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 81511a5b03dc..24bdabfa96da 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -164,7 +164,7 @@ func (d *diff) GetExpiryIterator() (iterator.Iterator[ExpiryEntry], error) { } func (d *diff) HasExpiry(entry ExpiryEntry) (bool, error) { - if has, modified := d.expiryDiff.hasExpiry(entry); modified { + if has, modified := d.expiryDiff.modified[entry]; modified { return has, nil } @@ -489,14 +489,12 @@ func (d *diff) Apply(baseState Chain) error { for subnetID, supply := range d.currentSupply { baseState.SetCurrentSupply(subnetID, supply) } - addedExpiryIterator := iterator.FromTree(d.expiryDiff.added) - for addedExpiryIterator.Next() { - entry := addedExpiryIterator.Value() - baseState.PutExpiry(entry) - } - addedExpiryIterator.Release() - for removed := range d.expiryDiff.removed { - baseState.DeleteExpiry(removed) + for entry, isAdded := range d.expiryDiff.modified { + if isAdded { + baseState.PutExpiry(entry) + } else { + baseState.DeleteExpiry(entry) + } } for _, subnetValidatorDiffs := range d.currentStakerDiffs.validatorDiffs { for _, validatorDiff := range subnetValidatorDiffs { diff --git a/vms/platformvm/state/expiry.go b/vms/platformvm/state/expiry.go index ccd37f17db4c..b50439ddf20c 100644 --- a/vms/platformvm/state/expiry.go +++ b/vms/platformvm/state/expiry.go @@ -13,7 +13,6 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/iterator" - "github.com/ava-labs/avalanchego/utils/set" ) // expiryEntry = [timestamp] + [validationID] @@ -82,48 +81,34 @@ func (e ExpiryEntry) Compare(o ExpiryEntry) int { } type expiryDiff struct { - added *btree.BTreeG[ExpiryEntry] - removed set.Set[ExpiryEntry] + modified map[ExpiryEntry]bool // bool represents isAdded + added *btree.BTreeG[ExpiryEntry] } func newExpiryDiff() *expiryDiff { return &expiryDiff{ - added: btree.NewG(defaultTreeDegree, ExpiryEntry.Less), + modified: make(map[ExpiryEntry]bool), + added: btree.NewG(defaultTreeDegree, ExpiryEntry.Less), } } func (e *expiryDiff) PutExpiry(entry ExpiryEntry) { + e.modified[entry] = true e.added.ReplaceOrInsert(entry) - e.removed.Remove(entry) } func (e *expiryDiff) DeleteExpiry(entry ExpiryEntry) { + e.modified[entry] = false e.added.Delete(entry) - e.removed.Add(entry) } func (e *expiryDiff) getExpiryIterator(parentIterator iterator.Iterator[ExpiryEntry]) iterator.Iterator[ExpiryEntry] { - // The iterators are deduplicated so that additions that were present in the - // parent iterator are not duplicated. - return iterator.Deduplicate( - iterator.Filter( - iterator.Merge( - ExpiryEntry.Less, - parentIterator, - iterator.FromTree(e.added), - ), - e.removed.Contains, - ), + return iterator.Merge( + ExpiryEntry.Less, + iterator.Filter(parentIterator, func(entry ExpiryEntry) bool { + _, ok := e.modified[entry] + return ok + }), + iterator.FromTree(e.added), ) } - -func (e *expiryDiff) hasExpiry(entry ExpiryEntry) (bool, bool) { - switch { - case e.removed.Contains(entry): - return false, true - case e.added.Has(entry): - return true, true - default: - return false, false - } -} diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 61991e7ac6db..65623442358b 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -703,7 +703,7 @@ func (s *state) GetExpiryIterator() (iterator.Iterator[ExpiryEntry], error) { } func (s *state) HasExpiry(entry ExpiryEntry) (bool, error) { - if has, modified := s.expiryDiff.hasExpiry(entry); modified { + if has, modified := s.expiryDiff.modified[entry]; modified { return has, nil } return s.expiry.Has(entry), nil @@ -1983,23 +1983,18 @@ func (s *state) GetBlockIDAtHeight(height uint64) (ids.ID, error) { } func (s *state) writeExpiry() error { - it := iterator.FromTree(s.expiryDiff.added) - defer it.Release() - - for it.Next() { - entry := it.Value() - s.expiry.ReplaceOrInsert(entry) - + for entry, isAdded := range s.expiryDiff.modified { key := entry.Marshal() - if err := s.expiryDB.Put(key, nil); err != nil { - return err - } - } - for removed := range s.expiryDiff.removed { - s.expiry.Delete(removed) - key := removed.Marshal() - if err := s.expiryDB.Delete(key); err != nil { + var err error + if isAdded { + s.expiry.ReplaceOrInsert(entry) + err = s.expiryDB.Put(key, nil) + } else { + s.expiry.Delete(entry) + err = s.expiryDB.Delete(key) + } + if err != nil { return err } } From d67408dac3f3bba6e7ba2b48a3c5d9bcb81d9fac Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 12 Sep 2024 16:53:05 -0400 Subject: [PATCH 032/199] nit --- vms/platformvm/state/state.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 65623442358b..50f00e1fe8f7 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1984,9 +1984,10 @@ func (s *state) GetBlockIDAtHeight(height uint64) (ids.ID, error) { func (s *state) writeExpiry() error { for entry, isAdded := range s.expiryDiff.modified { - key := entry.Marshal() - - var err error + var ( + key = entry.Marshal() + err error + ) if isAdded { s.expiry.ReplaceOrInsert(entry) err = s.expiryDB.Put(key, nil) From 6e457faec93519f76fb806a9390417c92f5b579e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 13 Sep 2024 10:10:54 -0400 Subject: [PATCH 033/199] Implement acp-77 state diff --- vms/platformvm/state/diff.go | 68 ++++++ vms/platformvm/state/state.go | 1 + vms/platformvm/state/subnet_only_validator.go | 205 ++++++++++++++++-- .../state/subnet_only_validator_test.go | 179 ++++++++++++--- 4 files changed, 405 insertions(+), 48 deletions(-) diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 24bdabfa96da..8d9eb5438fcf 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -43,6 +43,7 @@ type diff struct { currentSupply map[ids.ID]uint64 expiryDiff *expiryDiff + sovDiff *subnetOnlyValidatorsDiff currentStakerDiffs diffStakers // map of subnetID -> nodeID -> total accrued delegatee rewards @@ -82,6 +83,7 @@ func NewDiff( feeState: parentState.GetFeeState(), accruedFees: parentState.GetAccruedFees(), expiryDiff: newExpiryDiff(), + sovDiff: newSubnetOnlyValidatorsDiff(), subnetOwners: make(map[ids.ID]fx.Owner), subnetManagers: make(map[ids.ID]chainIDAndAddr), }, nil @@ -184,6 +186,67 @@ func (d *diff) DeleteExpiry(entry ExpiryEntry) { d.expiryDiff.DeleteExpiry(entry) } +func (d *diff) GetActiveSubnetOnlyValidatorsIterator() (iterator.Iterator[SubnetOnlyValidator], error) { + parentState, ok := d.stateVersions.GetState(d.parentID) + if !ok { + return nil, fmt.Errorf("%w: %s", ErrMissingParentState, d.parentID) + } + + parentIterator, err := parentState.GetActiveSubnetOnlyValidatorsIterator() + if err != nil { + return nil, err + } + + return d.sovDiff.getActiveSubnetOnlyValidatorsIterator(parentIterator), nil +} + +func (d *diff) NumActiveSubnetOnlyValidators() (int, error) { + parentState, ok := d.stateVersions.GetState(d.parentID) + if !ok { + return 0, fmt.Errorf("%w: %s", ErrMissingParentState, d.parentID) + } + + count, err := parentState.NumActiveSubnetOnlyValidators() + if err != nil { + return 0, err + } + + return count + d.sovDiff.numAddedActive, nil +} + +func (d *diff) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { + if sov, modified := d.sovDiff.modified[validationID]; modified { + if sov.Weight == 0 { + return SubnetOnlyValidator{}, database.ErrNotFound + } + return sov, nil + } + + parentState, ok := d.stateVersions.GetState(d.parentID) + if !ok { + return SubnetOnlyValidator{}, fmt.Errorf("%w: %s", ErrMissingParentState, d.parentID) + } + + return parentState.GetSubnetOnlyValidator(validationID) +} + +func (d *diff) HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool, error) { + if has, modified := d.sovDiff.hasSubnetOnlyValidator(subnetID, nodeID); modified { + return has, nil + } + + parentState, ok := d.stateVersions.GetState(d.parentID) + if !ok { + return false, fmt.Errorf("%w: %s", ErrMissingParentState, d.parentID) + } + + return parentState.HasSubnetOnlyValidator(subnetID, nodeID) +} + +func (d *diff) PutSubnetOnlyValidator(sov SubnetOnlyValidator) error { + return d.sovDiff.putSubnetOnlyValidator(d, sov) +} + func (d *diff) GetCurrentValidator(subnetID ids.ID, nodeID ids.NodeID) (*Staker, error) { // If the validator was modified in this diff, return the modified // validator. @@ -496,6 +559,11 @@ func (d *diff) Apply(baseState Chain) error { baseState.DeleteExpiry(entry) } } + for _, sov := range d.sovDiff.modified { + if err := baseState.PutSubnetOnlyValidator(sov); err != nil { + return err + } + } for _, subnetValidatorDiffs := range d.currentStakerDiffs.validatorDiffs { for _, validatorDiff := range subnetValidatorDiffs { switch validatorDiff.validatorStatus { diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 50f00e1fe8f7..b6961bf3f271 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -99,6 +99,7 @@ var ( // execution. type Chain interface { Expiry + SubnetOnlyValidators Stakers avax.UTXOAdder avax.UTXOGetter diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 5af028314e6c..cc30ca29f522 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -4,17 +4,57 @@ package state import ( + "bytes" + "errors" "fmt" "github.com/google/btree" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/vms/platformvm/block" ) -var _ btree.LessFunc[*SubnetOnlyValidator] = (*SubnetOnlyValidator).Less +var ( + _ btree.LessFunc[SubnetOnlyValidator] = SubnetOnlyValidator.Less + ErrMutatedSubnetOnlyValidator = errors.New("subnet only validator contains mutated constant fields") + ErrDuplicateSubnetOnlyValidator = errors.New("subnet only validator contains conflicting subnetID + nodeID pair") +) + +type SubnetOnlyValidators interface { + // GetActiveSubnetOnlyValidatorsIterator returns an iterator of all the + // active subnet only validators in increasing order of EndAccumulatedFee. + GetActiveSubnetOnlyValidatorsIterator() (iterator.Iterator[SubnetOnlyValidator], error) + + // NumActiveSubnetOnlyValidators returns the number of currently active + // subnet only validators. + NumActiveSubnetOnlyValidators() (int, error) + + // GetSubnetOnlyValidator returns the validator with [validationID] if it + // exists. If the validator does not exist, [err] will equal + // [database.ErrNotFound]. + GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) + + // HasSubnetOnlyValidator returns the validator with [validationID] if it + // exists. If the validator does not exist, [err] will equal + // [database.ErrNotFound]. + HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool, error) + + // PutSubnetOnlyValidator inserts [sov] as a validator. + // + // If inserting this validator attempts to modify any of the constant fields + // of the subnet only validator struct, an error will be returned. + // + // If inserting this validator would cause the mapping of subnetID+nodeID to + // validationID to be non-unique, an error will be returned. + PutSubnetOnlyValidator(sov SubnetOnlyValidator) error +} + +// SubnetOnlyValidator defines an ACP-77 validator. For a given ValidationID, it +// is expected for SubnetID, NodeID, PublicKey, RemainingBalanceOwner, and +// StartTime to be constant. type SubnetOnlyValidator struct { // ValidationID is not serialized because it is used as the key in the // database, so it doesn't need to be stored in the value. @@ -27,6 +67,10 @@ type SubnetOnlyValidator struct { // guaranteed to be populated. PublicKey []byte `serialize:"true"` + // RemainingBalanceOwner is the owner that will be used when returning the + // balance of the validator after removing accrued fees. + RemainingBalanceOwner []byte `serialize:"true"` + // StartTime is the unix timestamp, in seconds, when this validator was // added to the set. StartTime uint64 `serialize:"true"` @@ -46,44 +90,62 @@ type SubnetOnlyValidator struct { // accrue before this validator must be deactivated. It is equal to the // amount of fees this validator is willing to pay plus the amount of // globally accumulated fees when this validator started validating. + // + // If this value is 0, the validator is inactive. EndAccumulatedFee uint64 `serialize:"true"` } -// Less determines a canonical ordering of *SubnetOnlyValidators based on their -// EndAccumulatedFees and ValidationIDs. -// -// Returns true if: -// -// 1. This validator has a lower EndAccumulatedFee than the other. -// 2. This validator has an equal EndAccumulatedFee to the other and has a -// lexicographically lower ValidationID. -func (v *SubnetOnlyValidator) Less(o *SubnetOnlyValidator) bool { +func (v SubnetOnlyValidator) Less(o SubnetOnlyValidator) bool { + return v.Compare(o) == -1 +} + +// Compare determines a canonical ordering of *SubnetOnlyValidators based on +// their EndAccumulatedFees and ValidationIDs. Lower EndAccumulatedFees result +// in an earlier ordering. +func (v SubnetOnlyValidator) Compare(o SubnetOnlyValidator) int { switch { case v.EndAccumulatedFee < o.EndAccumulatedFee: - return true + return -1 case o.EndAccumulatedFee < v.EndAccumulatedFee: - return false + return 1 default: - return v.ValidationID.Compare(o.ValidationID) == -1 + return v.ValidationID.Compare(o.ValidationID) + } +} + +// validateConstants returns true if the constants of this validator have not +// been modified. +func (v SubnetOnlyValidator) validateConstants(o SubnetOnlyValidator) bool { + if v.ValidationID != o.ValidationID { + return true } + return v.SubnetID == o.SubnetID && + v.NodeID == o.NodeID && + bytes.Equal(v.PublicKey, o.PublicKey) && + bytes.Equal(v.RemainingBalanceOwner, o.RemainingBalanceOwner) && + v.StartTime == o.StartTime } -func getSubnetOnlyValidator(db database.KeyValueReader, validationID ids.ID) (*SubnetOnlyValidator, error) { +func (v SubnetOnlyValidator) isActive() bool { + return v.Weight != 0 && v.EndAccumulatedFee != 0 +} + +func getSubnetOnlyValidator(db database.KeyValueReader, validationID ids.ID) (SubnetOnlyValidator, error) { bytes, err := db.Get(validationID[:]) if err != nil { - return nil, err + return SubnetOnlyValidator{}, err } - vdr := &SubnetOnlyValidator{ + vdr := SubnetOnlyValidator{ ValidationID: validationID, } - if _, err = block.GenesisCodec.Unmarshal(bytes, vdr); err != nil { - return nil, fmt.Errorf("failed to unmarshal SubnetOnlyValidator: %w", err) + if _, err = block.GenesisCodec.Unmarshal(bytes, &vdr); err != nil { + return SubnetOnlyValidator{}, fmt.Errorf("failed to unmarshal SubnetOnlyValidator: %w", err) } return vdr, err } -func putSubnetOnlyValidator(db database.KeyValueWriter, vdr *SubnetOnlyValidator) error { +func putSubnetOnlyValidator(db database.KeyValueWriter, vdr SubnetOnlyValidator) error { bytes, err := block.GenesisCodec.Marshal(block.CodecVersion, vdr) if err != nil { return fmt.Errorf("failed to marshal SubnetOnlyValidator: %w", err) @@ -94,3 +156,108 @@ func putSubnetOnlyValidator(db database.KeyValueWriter, vdr *SubnetOnlyValidator func deleteSubnetOnlyValidator(db database.KeyValueDeleter, validationID ids.ID) error { return db.Delete(validationID[:]) } + +type subnetIDNodeID struct { + subnetID ids.ID + nodeID ids.NodeID +} + +type subnetOnlyValidatorsDiff struct { + numAddedActive int // May be negative + modified map[ids.ID]SubnetOnlyValidator + modifiedHasNodeIDs map[subnetIDNodeID]bool + active *btree.BTreeG[SubnetOnlyValidator] +} + +func newSubnetOnlyValidatorsDiff() *subnetOnlyValidatorsDiff { + return &subnetOnlyValidatorsDiff{ + modified: make(map[ids.ID]SubnetOnlyValidator), + modifiedHasNodeIDs: make(map[subnetIDNodeID]bool), + active: btree.NewG(defaultTreeDegree, SubnetOnlyValidator.Less), + } +} + +func (d *subnetOnlyValidatorsDiff) getActiveSubnetOnlyValidatorsIterator(parentIterator iterator.Iterator[SubnetOnlyValidator]) iterator.Iterator[SubnetOnlyValidator] { + return iterator.Merge( + SubnetOnlyValidator.Less, + iterator.Filter(parentIterator, func(sov SubnetOnlyValidator) bool { + _, ok := d.modified[sov.ValidationID] + return ok + }), + iterator.FromTree(d.active), + ) +} + +func (d *subnetOnlyValidatorsDiff) hasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool, bool) { + subnetIDNodeID := subnetIDNodeID{ + subnetID: subnetID, + nodeID: nodeID, + } + has, modified := d.modifiedHasNodeIDs[subnetIDNodeID] + return has, modified +} + +func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state SubnetOnlyValidators, sov SubnetOnlyValidator) error { + diff, err := numActiveSubnetOnlyValidatorChange(state, sov) + if err != nil { + return err + } + d.numAddedActive += diff + + if prevSOV, ok := d.modified[sov.ValidationID]; ok { + prevSubnetIDNodeID := subnetIDNodeID{ + subnetID: prevSOV.SubnetID, + nodeID: prevSOV.NodeID, + } + d.modifiedHasNodeIDs[prevSubnetIDNodeID] = false + d.active.Delete(prevSOV) + } + d.modified[sov.ValidationID] = sov + + subnetIDNodeID := subnetIDNodeID{ + subnetID: sov.SubnetID, + nodeID: sov.NodeID, + } + isDeleted := sov.Weight == 0 + d.modifiedHasNodeIDs[subnetIDNodeID] = !isDeleted + if isDeleted || sov.EndAccumulatedFee == 0 { + // Validator is being deleted or is inactive + return nil + } + d.active.ReplaceOrInsert(sov) + return nil +} + +// numActiveSubnetOnlyValidatorChange returns the change in the number of active +// subnet only validators if [sov] were to be inserted into [state]. If it is +// invalid for [sov] to be inserted, an error is returned. +func numActiveSubnetOnlyValidatorChange(state SubnetOnlyValidators, sov SubnetOnlyValidator) (int, error) { + switch priorSOV, err := state.GetSubnetOnlyValidator(sov.ValidationID); err { + case nil: + if !priorSOV.validateConstants(sov) { + return 0, ErrMutatedSubnetOnlyValidator + } + switch { + case !priorSOV.isActive() && sov.isActive(): + return 1, nil // Increasing the number of active validators + case priorSOV.isActive() && !sov.isActive(): + return -1, nil // Decreasing the number of active validators + default: + return 0, nil + } + case database.ErrNotFound: + has, err := state.HasSubnetOnlyValidator(sov.SubnetID, sov.NodeID) + if err != nil { + return 0, err + } + if has { + return 0, ErrDuplicateSubnetOnlyValidator + } + if sov.isActive() { + return 1, nil // Increasing the number of active validators + } + return 0, nil // Adding an inactive validator + default: + return 0, err + } +} diff --git a/vms/platformvm/state/subnet_only_validator_test.go b/vms/platformvm/state/subnet_only_validator_test.go index bcbb21e0027c..e76c4b73a242 100644 --- a/vms/platformvm/state/subnet_only_validator_test.go +++ b/vms/platformvm/state/subnet_only_validator_test.go @@ -12,62 +12,171 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/vms/platformvm/block" + "github.com/ava-labs/avalanchego/vms/platformvm/fx" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) -func TestSubnetOnlyValidator_Less(t *testing.T) { +func TestSubnetOnlyValidator_Compare(t *testing.T) { tests := []struct { - name string - v *SubnetOnlyValidator - o *SubnetOnlyValidator - equal bool + name string + v SubnetOnlyValidator + o SubnetOnlyValidator + expected int }{ { name: "v.EndAccumulatedFee < o.EndAccumulatedFee", - v: &SubnetOnlyValidator{ + v: SubnetOnlyValidator{ ValidationID: ids.GenerateTestID(), EndAccumulatedFee: 1, }, - o: &SubnetOnlyValidator{ + o: SubnetOnlyValidator{ ValidationID: ids.GenerateTestID(), EndAccumulatedFee: 2, }, - equal: false, + expected: -1, }, { name: "v.EndAccumulatedFee = o.EndAccumulatedFee, v.ValidationID < o.ValidationID", - v: &SubnetOnlyValidator{ + v: SubnetOnlyValidator{ ValidationID: ids.ID{0}, EndAccumulatedFee: 1, }, - o: &SubnetOnlyValidator{ + o: SubnetOnlyValidator{ ValidationID: ids.ID{1}, EndAccumulatedFee: 1, }, - equal: false, + expected: -1, }, { name: "v.EndAccumulatedFee = o.EndAccumulatedFee, v.ValidationID = o.ValidationID", - v: &SubnetOnlyValidator{ + v: SubnetOnlyValidator{ ValidationID: ids.ID{0}, EndAccumulatedFee: 1, }, - o: &SubnetOnlyValidator{ + o: SubnetOnlyValidator{ ValidationID: ids.ID{0}, EndAccumulatedFee: 1, }, - equal: true, + expected: 0, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { require := require.New(t) - less := test.v.Less(test.o) - require.Equal(!test.equal, less) + require.Equal(test.expected, test.v.Compare(test.o)) + require.Equal(-test.expected, test.o.Compare(test.v)) + require.Equal(test.expected == -1, test.v.Less(test.o)) + require.False(test.o.Less(test.v)) + }) + } +} + +func TestSubnetOnlyValidator_validateConstants(t *testing.T) { + sov := SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: utils.RandomBytes(bls.PublicKeyLen), + RemainingBalanceOwner: utils.RandomBytes(32), + StartTime: rand.Uint64(), // #nosec G404 + } + + tests := []struct { + name string + v SubnetOnlyValidator + expected bool + }{ + { + name: "equal", + v: sov, + expected: true, + }, + { + name: "everything is different", + v: SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: utils.RandomBytes(bls.PublicKeyLen), + RemainingBalanceOwner: utils.RandomBytes(32), + StartTime: rand.Uint64(), // #nosec G404 + }, + expected: true, + }, + { + name: "different subnetID", + v: SubnetOnlyValidator{ + ValidationID: sov.ValidationID, + SubnetID: ids.GenerateTestID(), + NodeID: sov.NodeID, + PublicKey: sov.PublicKey, + RemainingBalanceOwner: sov.RemainingBalanceOwner, + StartTime: sov.StartTime, + }, + }, + { + name: "different nodeID", + v: SubnetOnlyValidator{ + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: ids.GenerateTestNodeID(), + PublicKey: sov.PublicKey, + RemainingBalanceOwner: sov.RemainingBalanceOwner, + StartTime: sov.StartTime, + }, + }, + { + name: "different publicKey", + v: SubnetOnlyValidator{ + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: utils.RandomBytes(bls.PublicKeyLen), + RemainingBalanceOwner: sov.RemainingBalanceOwner, + StartTime: sov.StartTime, + }, + }, + { + name: "different remainingBalanceOwner", + v: SubnetOnlyValidator{ + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: sov.PublicKey, + RemainingBalanceOwner: utils.RandomBytes(32), + StartTime: sov.StartTime, + }, + }, + { + name: "different startTime", + v: SubnetOnlyValidator{ + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: sov.PublicKey, + RemainingBalanceOwner: sov.RemainingBalanceOwner, + StartTime: rand.Uint64(), // #nosec G404 + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + sov := sov + v := test.v + + randomize := func(v *SubnetOnlyValidator) { + v.Weight = rand.Uint64() // #nosec G404 + v.MinNonce = rand.Uint64() // #nosec G404 + v.EndAccumulatedFee = rand.Uint64() // #nosec G404 + } + randomize(&sov) + randomize(&v) - greater := test.o.Less(test.v) - require.False(greater) + require.Equal(t, test.expected, sov.validateConstants(v)) }) } } @@ -78,22 +187,34 @@ func TestSubnetOnlyValidator_DatabaseHelpers(t *testing.T) { sk, err := bls.NewSecretKey() require.NoError(err) + pk := bls.PublicFromSecretKey(sk) + pkBytes := bls.PublicKeyToUncompressedBytes(pk) + + var owner fx.Owner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + } + ownerBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &owner) + require.NoError(err) - vdr := &SubnetOnlyValidator{ - ValidationID: ids.GenerateTestID(), - SubnetID: ids.GenerateTestID(), - NodeID: ids.GenerateTestNodeID(), - PublicKey: bls.PublicKeyToUncompressedBytes(bls.PublicFromSecretKey(sk)), - StartTime: rand.Uint64(), // #nosec G404 - Weight: rand.Uint64(), // #nosec G404 - MinNonce: rand.Uint64(), // #nosec G404 - EndAccumulatedFee: rand.Uint64(), // #nosec G404 + vdr := SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: pkBytes, + RemainingBalanceOwner: ownerBytes, + StartTime: rand.Uint64(), // #nosec G404 + Weight: rand.Uint64(), // #nosec G404 + MinNonce: rand.Uint64(), // #nosec G404 + EndAccumulatedFee: rand.Uint64(), // #nosec G404 } // Validator hasn't been put on disk yet gotVdr, err := getSubnetOnlyValidator(db, vdr.ValidationID) require.ErrorIs(err, database.ErrNotFound) - require.Nil(gotVdr) + require.Zero(gotVdr) // Place the validator on disk require.NoError(putSubnetOnlyValidator(db, vdr)) @@ -109,5 +230,5 @@ func TestSubnetOnlyValidator_DatabaseHelpers(t *testing.T) { // Verify that the validator has been removed from disk gotVdr, err = getSubnetOnlyValidator(db, vdr.ValidationID) require.ErrorIs(err, database.ErrNotFound) - require.Nil(gotVdr) + require.Zero(gotVdr) } From 86754d0e366b98a6551f45450c9b0e4fd2ae1fed Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 13 Sep 2024 10:43:21 -0400 Subject: [PATCH 034/199] Fix apply reordering --- vms/platformvm/state/diff.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 8d9eb5438fcf..e63358e1a515 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -559,7 +559,22 @@ func (d *diff) Apply(baseState Chain) error { baseState.DeleteExpiry(entry) } } + // Ensure that all sov deletions happen before any sov additions. This + // ensures that a subnetID+nodeID pair that was deleted and then re-added in + // a single diff can't get reordered into the addition happening first; + // which would return an error. for _, sov := range d.sovDiff.modified { + if sov.Weight != 0 { + continue + } + if err := baseState.PutSubnetOnlyValidator(sov); err != nil { + return err + } + } + for _, sov := range d.sovDiff.modified { + if sov.Weight == 0 { + continue + } if err := baseState.PutSubnetOnlyValidator(sov); err != nil { return err } From c68afecbe471206e61bac2fc92bb03cdf1aa228c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 13 Sep 2024 23:11:12 -0400 Subject: [PATCH 035/199] Implement acp-77 state diff without historical diffs --- .../block/executor/verifier_test.go | 5 + vms/platformvm/state/diff.go | 40 +- vms/platformvm/state/diff_test.go | 476 ++++++++++++++++-- vms/platformvm/state/mock_chain.go | 73 +++ vms/platformvm/state/mock_diff.go | 73 +++ vms/platformvm/state/mock_state.go | 73 +++ vms/platformvm/state/state.go | 172 +++++++ vms/platformvm/state/subnet_only_validator.go | 6 +- 8 files changed, 845 insertions(+), 73 deletions(-) diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 5b786b0e33d8..bf44ff70d7a6 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -104,6 +104,7 @@ func TestVerifierVisitProposalBlock(t *testing.T) { parentOnAcceptState.EXPECT().GetTimestamp().Return(timestamp).Times(2) parentOnAcceptState.EXPECT().GetFeeState().Return(gas.State{}).Times(2) parentOnAcceptState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(2) + parentOnAcceptState.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(2) backend := &backend{ lastAccepted: parentID, @@ -336,6 +337,7 @@ func TestVerifierVisitStandardBlock(t *testing.T) { parentState.EXPECT().GetTimestamp().Return(timestamp).Times(1) parentState.EXPECT().GetFeeState().Return(gas.State{}).Times(1) parentState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + parentState.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) mempool.EXPECT().Remove(apricotBlk.Txs()).Times(1) @@ -598,6 +600,7 @@ func TestBanffAbortBlockTimestampChecks(t *testing.T) { s.EXPECT().GetTimestamp().Return(parentTime).Times(3) s.EXPECT().GetFeeState().Return(gas.State{}).Times(3) s.EXPECT().GetAccruedFees().Return(uint64(0)).Times(3) + s.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(3) onDecisionState, err := state.NewDiff(parentID, backend) require.NoError(err) @@ -696,6 +699,7 @@ func TestBanffCommitBlockTimestampChecks(t *testing.T) { s.EXPECT().GetTimestamp().Return(parentTime).Times(3) s.EXPECT().GetFeeState().Return(gas.State{}).Times(3) s.EXPECT().GetAccruedFees().Return(uint64(0)).Times(3) + s.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(3) onDecisionState, err := state.NewDiff(parentID, backend) require.NoError(err) @@ -812,6 +816,7 @@ func TestVerifierVisitStandardBlockWithDuplicateInputs(t *testing.T) { parentState.EXPECT().GetTimestamp().Return(timestamp).Times(1) parentState.EXPECT().GetFeeState().Return(gas.State{}).Times(1) parentState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + parentState.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) parentStatelessBlk.EXPECT().Parent().Return(grandParentID).Times(1) err = verifier.ApricotStandardBlock(blk) diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index e63358e1a515..deb09026282d 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -35,9 +35,10 @@ type diff struct { parentID ids.ID stateVersions Versions - timestamp time.Time - feeState gas.State - accruedFees uint64 + timestamp time.Time + feeState gas.State + accruedFees uint64 + parentActiveSOVs int // Subnet ID --> supply of native asset of the subnet currentSupply map[ids.ID]uint64 @@ -77,15 +78,16 @@ func NewDiff( return nil, fmt.Errorf("%w: %s", ErrMissingParentState, parentID) } return &diff{ - parentID: parentID, - stateVersions: stateVersions, - timestamp: parentState.GetTimestamp(), - feeState: parentState.GetFeeState(), - accruedFees: parentState.GetAccruedFees(), - expiryDiff: newExpiryDiff(), - sovDiff: newSubnetOnlyValidatorsDiff(), - subnetOwners: make(map[ids.ID]fx.Owner), - subnetManagers: make(map[ids.ID]chainIDAndAddr), + parentID: parentID, + stateVersions: stateVersions, + timestamp: parentState.GetTimestamp(), + feeState: parentState.GetFeeState(), + accruedFees: parentState.GetAccruedFees(), + parentActiveSOVs: parentState.NumActiveSubnetOnlyValidators(), + expiryDiff: newExpiryDiff(), + sovDiff: newSubnetOnlyValidatorsDiff(), + subnetOwners: make(map[ids.ID]fx.Owner), + subnetManagers: make(map[ids.ID]chainIDAndAddr), }, nil } @@ -200,18 +202,8 @@ func (d *diff) GetActiveSubnetOnlyValidatorsIterator() (iterator.Iterator[Subnet return d.sovDiff.getActiveSubnetOnlyValidatorsIterator(parentIterator), nil } -func (d *diff) NumActiveSubnetOnlyValidators() (int, error) { - parentState, ok := d.stateVersions.GetState(d.parentID) - if !ok { - return 0, fmt.Errorf("%w: %s", ErrMissingParentState, d.parentID) - } - - count, err := parentState.NumActiveSubnetOnlyValidators() - if err != nil { - return 0, err - } - - return count + d.sovDiff.numAddedActive, nil +func (d *diff) NumActiveSubnetOnlyValidators() int { + return d.parentActiveSOVs + d.sovDiff.numAddedActive } func (d *diff) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index b71599928e23..a34a5b40c8f2 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -4,6 +4,7 @@ package state import ( + "math/rand" "testing" "time" @@ -202,64 +203,429 @@ func TestDiffExpiry(t *testing.T) { } for _, test := range tests { - require := require.New(t) + t.Run(test.name, func(t *testing.T) { + require := require.New(t) - state := newTestState(t, memdb.New()) - for _, expiry := range test.initialExpiries { - state.PutExpiry(expiry) - } + state := newTestState(t, memdb.New()) + for _, expiry := range test.initialExpiries { + state.PutExpiry(expiry) + } - d, err := NewDiffOn(state) - require.NoError(err) + d, err := NewDiffOn(state) + require.NoError(err) - var ( - expectedExpiries = set.Of(test.initialExpiries...) - unexpectedExpiries set.Set[ExpiryEntry] - ) - for _, op := range test.ops { - if op.put { - d.PutExpiry(op.entry) - expectedExpiries.Add(op.entry) - unexpectedExpiries.Remove(op.entry) - } else { - d.DeleteExpiry(op.entry) - expectedExpiries.Remove(op.entry) - unexpectedExpiries.Add(op.entry) + var ( + expectedExpiries = set.Of(test.initialExpiries...) + unexpectedExpiries set.Set[ExpiryEntry] + ) + for _, op := range test.ops { + if op.put { + d.PutExpiry(op.entry) + expectedExpiries.Add(op.entry) + unexpectedExpiries.Remove(op.entry) + } else { + d.DeleteExpiry(op.entry) + expectedExpiries.Remove(op.entry) + unexpectedExpiries.Add(op.entry) + } } - } - // If expectedExpiries is empty, we want expectedExpiriesSlice to be - // nil. - var expectedExpiriesSlice []ExpiryEntry - if expectedExpiries.Len() > 0 { - expectedExpiriesSlice = expectedExpiries.List() - utils.Sort(expectedExpiriesSlice) - } + // If expectedExpiries is empty, we want expectedExpiriesSlice to be + // nil. + var expectedExpiriesSlice []ExpiryEntry + if expectedExpiries.Len() > 0 { + expectedExpiriesSlice = expectedExpiries.List() + utils.Sort(expectedExpiriesSlice) + } - verifyChain := func(chain Chain) { - expiryIterator, err := chain.GetExpiryIterator() + verifyChain := func(chain Chain) { + expiryIterator, err := chain.GetExpiryIterator() + require.NoError(err) + require.Equal( + expectedExpiriesSlice, + iterator.ToSlice(expiryIterator), + ) + + for expiry := range expectedExpiries { + has, err := chain.HasExpiry(expiry) + require.NoError(err) + require.True(has) + } + for expiry := range unexpectedExpiries { + has, err := chain.HasExpiry(expiry) + require.NoError(err) + require.False(has) + } + } + + verifyChain(d) + require.NoError(d.Apply(state)) + verifyChain(state) + assertChainsEqual(t, d, state) + }) + } +} + +func TestDiffSubnetOnlyValidators(t *testing.T) { + sov := SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + } + + tests := []struct { + name string + initial []SubnetOnlyValidator + sovs []SubnetOnlyValidator + }{ + { + name: "empty noop", + }, + { + name: "initially active not modified", + initial: []SubnetOnlyValidator{ + { + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + }, + { + name: "initially inactive not modified", + initial: []SubnetOnlyValidator{ + { + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + Weight: 1, // Not removed + EndAccumulatedFee: 0, // Inactive + }, + }, + }, + { + name: "initially active removed", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 0, // Removed + }, + }, + }, + { + name: "initially inactive removed", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 1, // Not removed + EndAccumulatedFee: 0, // Inactive + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 0, // Removed + }, + }, + }, + { + name: "increase active weight", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 2, // Increased + EndAccumulatedFee: 1, // Active + }, + }, + }, + { + name: "deactivate", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 1, // Not removed + EndAccumulatedFee: 0, // Inactive + }, + }, + }, + { + name: "reactivate", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 1, // Not removed + EndAccumulatedFee: 0, // Inactive + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + }, + { + name: "update multiple times", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 2, // Not removed + EndAccumulatedFee: 1, // Inactive + }, + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 3, // Not removed + EndAccumulatedFee: 1, // Inactive + }, + }, + }, + { + name: "change validationID", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 0, // Removed + }, + { + ValidationID: ids.GenerateTestID(), + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Inactive + }, + }, + }, + { + name: "added and removed", + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + Weight: 0, // Removed + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + state := newTestState(t, memdb.New()) + expectedSOVs := make(map[ids.ID]SubnetOnlyValidator) + for _, sov := range test.initial { + require.NoError(state.PutSubnetOnlyValidator(sov)) + expectedSOVs[sov.ValidationID] = sov + } + + d, err := NewDiffOn(state) require.NoError(err) - require.Equal( - expectedExpiriesSlice, - iterator.ToSlice(expiryIterator), - ) - for expiry := range expectedExpiries { - has, err := chain.HasExpiry(expiry) - require.NoError(err) - require.True(has) + for _, sov := range test.sovs { + require.NoError(d.PutSubnetOnlyValidator(sov)) + expectedSOVs[sov.ValidationID] = sov } - for expiry := range unexpectedExpiries { - has, err := chain.HasExpiry(expiry) + + verifyChain := func(chain Chain) { + for _, expectedSOV := range expectedSOVs { + if expectedSOV.Weight != 0 { + continue + } + + sov, err := chain.GetSubnetOnlyValidator(expectedSOV.ValidationID) + require.ErrorIs(err, database.ErrNotFound) + require.Zero(sov) + } + + var expectedActive []SubnetOnlyValidator + for _, expectedSOV := range expectedSOVs { + if expectedSOV.Weight == 0 { + continue + } + + sov, err := chain.GetSubnetOnlyValidator(expectedSOV.ValidationID) + require.NoError(err) + require.Equal(expectedSOV, sov) + + has, err := chain.HasSubnetOnlyValidator(expectedSOV.SubnetID, expectedSOV.NodeID) + require.NoError(err) + require.True(has) + + if expectedSOV.isActive() { + expectedActive = append(expectedActive, expectedSOV) + } + } + utils.Sort(expectedActive) + + activeIterator, err := chain.GetActiveSubnetOnlyValidatorsIterator() require.NoError(err) - require.False(has) + require.Equal( + expectedActive, + iterator.ToSlice(activeIterator), + ) + + require.Equal(len(expectedActive), chain.NumActiveSubnetOnlyValidators()) } - } - verifyChain(d) - require.NoError(d.Apply(state)) - verifyChain(state) - assertChainsEqual(t, d, state) + verifyChain(d) + require.NoError(d.Apply(state)) + verifyChain(state) + assertChainsEqual(t, state, d) + }) + } +} + +func TestDiffSubnetOnlyValidatorsErrors(t *testing.T) { + sov := SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + Weight: 1, // Not removed + } + + tests := []struct { + name string + initialEndAccumulatedFee uint64 + sov SubnetOnlyValidator + expectedErr error + }{ + { + name: "mutate active constants", + initialEndAccumulatedFee: 1, + sov: SubnetOnlyValidator{ + ValidationID: sov.ValidationID, + NodeID: ids.GenerateTestNodeID(), + }, + expectedErr: ErrMutatedSubnetOnlyValidator, + }, + { + name: "mutate inactive constants", + initialEndAccumulatedFee: 0, + sov: SubnetOnlyValidator{ + ValidationID: sov.ValidationID, + NodeID: ids.GenerateTestNodeID(), + }, + expectedErr: ErrMutatedSubnetOnlyValidator, + }, + { + name: "duplicate active subnetID and nodeID pair", + initialEndAccumulatedFee: 1, + sov: SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + NodeID: sov.NodeID, + }, + expectedErr: ErrDuplicateSubnetOnlyValidator, + }, + { + name: "duplicate inactive subnetID and nodeID pair", + initialEndAccumulatedFee: 0, + sov: SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + NodeID: sov.NodeID, + }, + expectedErr: ErrDuplicateSubnetOnlyValidator, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + state := newTestState(t, memdb.New()) + + sov.EndAccumulatedFee = test.initialEndAccumulatedFee + require.NoError(state.PutSubnetOnlyValidator(sov)) + + d, err := NewDiffOn(state) + require.NoError(err) + + // Initialize subnetID, weight, and endAccumulatedFee as they are + // constant among all tests. + test.sov.SubnetID = sov.SubnetID + test.sov.Weight = 1 // Not removed + test.sov.EndAccumulatedFee = rand.Uint64() //#nosec G404 + err = d.PutSubnetOnlyValidator(test.sov) + require.ErrorIs(err, test.expectedErr) + + // The invalid addition should not have modified the diff. + assertChainsEqual(t, state, d) + }) } } @@ -272,6 +638,7 @@ func TestDiffCurrentValidator(t *testing.T) { state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + state.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) d, err := NewDiffOn(state) require.NoError(err) @@ -307,6 +674,7 @@ func TestDiffPendingValidator(t *testing.T) { state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + state.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) d, err := NewDiffOn(state) require.NoError(err) @@ -348,6 +716,7 @@ func TestDiffCurrentDelegator(t *testing.T) { state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + state.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) d, err := NewDiffOn(state) require.NoError(err) @@ -395,6 +764,7 @@ func TestDiffPendingDelegator(t *testing.T) { state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + state.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) d, err := NewDiffOn(state) require.NoError(err) @@ -536,6 +906,7 @@ func TestDiffTx(t *testing.T) { state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + state.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) d, err := NewDiffOn(state) require.NoError(err) @@ -634,6 +1005,7 @@ func TestDiffUTXO(t *testing.T) { state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + state.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) d, err := NewDiffOn(state) require.NoError(err) @@ -688,6 +1060,18 @@ func assertChainsEqual(t *testing.T, expected, actual Chain) { ) } + expectedActiveSOVsIterator, expectedErr := expected.GetActiveSubnetOnlyValidatorsIterator() + actualActiveSOVsIterator, actualErr := actual.GetActiveSubnetOnlyValidatorsIterator() + require.Equal(expectedErr, actualErr) + if expectedErr == nil { + require.Equal( + iterator.ToSlice(expectedActiveSOVsIterator), + iterator.ToSlice(actualActiveSOVsIterator), + ) + } + + require.Equal(expected.NumActiveSubnetOnlyValidators(), actual.NumActiveSubnetOnlyValidators()) + expectedCurrentStakerIterator, expectedErr := expected.GetCurrentStakerIterator() actualCurrentStakerIterator, actualErr := actual.GetCurrentStakerIterator() require.Equal(expectedErr, actualErr) diff --git a/vms/platformvm/state/mock_chain.go b/vms/platformvm/state/mock_chain.go index 3b380a87a8b8..2ce5a181a684 100644 --- a/vms/platformvm/state/mock_chain.go +++ b/vms/platformvm/state/mock_chain.go @@ -204,6 +204,21 @@ func (mr *MockChainMockRecorder) GetAccruedFees() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccruedFees", reflect.TypeOf((*MockChain)(nil).GetAccruedFees)) } +// GetActiveSubnetOnlyValidatorsIterator mocks base method. +func (m *MockChain) GetActiveSubnetOnlyValidatorsIterator() (iterator.Iterator[SubnetOnlyValidator], error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetActiveSubnetOnlyValidatorsIterator") + ret0, _ := ret[0].(iterator.Iterator[SubnetOnlyValidator]) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetActiveSubnetOnlyValidatorsIterator indicates an expected call of GetActiveSubnetOnlyValidatorsIterator. +func (mr *MockChainMockRecorder) GetActiveSubnetOnlyValidatorsIterator() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveSubnetOnlyValidatorsIterator", reflect.TypeOf((*MockChain)(nil).GetActiveSubnetOnlyValidatorsIterator)) +} + // GetCurrentDelegatorIterator mocks base method. func (m *MockChain) GetCurrentDelegatorIterator(subnetID ids.ID, nodeID ids.NodeID) (iterator.Iterator[*Staker], error) { m.ctrl.T.Helper() @@ -369,6 +384,21 @@ func (mr *MockChainMockRecorder) GetSubnetManager(subnetID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetManager", reflect.TypeOf((*MockChain)(nil).GetSubnetManager), subnetID) } +// GetSubnetOnlyValidator mocks base method. +func (m *MockChain) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSubnetOnlyValidator", validationID) + ret0, _ := ret[0].(SubnetOnlyValidator) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSubnetOnlyValidator indicates an expected call of GetSubnetOnlyValidator. +func (mr *MockChainMockRecorder) GetSubnetOnlyValidator(validationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetOnlyValidator", reflect.TypeOf((*MockChain)(nil).GetSubnetOnlyValidator), validationID) +} + // GetSubnetOwner mocks base method. func (m *MockChain) GetSubnetOwner(subnetID ids.ID) (fx.Owner, error) { m.ctrl.T.Helper() @@ -459,6 +489,35 @@ func (mr *MockChainMockRecorder) HasExpiry(arg0 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasExpiry", reflect.TypeOf((*MockChain)(nil).HasExpiry), arg0) } +// HasSubnetOnlyValidator mocks base method. +func (m *MockChain) HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasSubnetOnlyValidator", subnetID, nodeID) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasSubnetOnlyValidator indicates an expected call of HasSubnetOnlyValidator. +func (mr *MockChainMockRecorder) HasSubnetOnlyValidator(subnetID, nodeID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasSubnetOnlyValidator", reflect.TypeOf((*MockChain)(nil).HasSubnetOnlyValidator), subnetID, nodeID) +} + +// NumActiveSubnetOnlyValidators mocks base method. +func (m *MockChain) NumActiveSubnetOnlyValidators() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NumActiveSubnetOnlyValidators") + ret0, _ := ret[0].(int) + return ret0 +} + +// NumActiveSubnetOnlyValidators indicates an expected call of NumActiveSubnetOnlyValidators. +func (mr *MockChainMockRecorder) NumActiveSubnetOnlyValidators() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumActiveSubnetOnlyValidators", reflect.TypeOf((*MockChain)(nil).NumActiveSubnetOnlyValidators)) +} + // PutCurrentDelegator mocks base method. func (m *MockChain) PutCurrentDelegator(staker *Staker) { m.ctrl.T.Helper() @@ -523,6 +582,20 @@ func (mr *MockChainMockRecorder) PutPendingValidator(staker any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutPendingValidator", reflect.TypeOf((*MockChain)(nil).PutPendingValidator), staker) } +// PutSubnetOnlyValidator mocks base method. +func (m *MockChain) PutSubnetOnlyValidator(sov SubnetOnlyValidator) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PutSubnetOnlyValidator", sov) + ret0, _ := ret[0].(error) + return ret0 +} + +// PutSubnetOnlyValidator indicates an expected call of PutSubnetOnlyValidator. +func (mr *MockChainMockRecorder) PutSubnetOnlyValidator(sov any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutSubnetOnlyValidator", reflect.TypeOf((*MockChain)(nil).PutSubnetOnlyValidator), sov) +} + // SetAccruedFees mocks base method. func (m *MockChain) SetAccruedFees(f uint64) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/mock_diff.go b/vms/platformvm/state/mock_diff.go index 77edfde92aaf..c2138705dc66 100644 --- a/vms/platformvm/state/mock_diff.go +++ b/vms/platformvm/state/mock_diff.go @@ -218,6 +218,21 @@ func (mr *MockDiffMockRecorder) GetAccruedFees() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccruedFees", reflect.TypeOf((*MockDiff)(nil).GetAccruedFees)) } +// GetActiveSubnetOnlyValidatorsIterator mocks base method. +func (m *MockDiff) GetActiveSubnetOnlyValidatorsIterator() (iterator.Iterator[SubnetOnlyValidator], error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetActiveSubnetOnlyValidatorsIterator") + ret0, _ := ret[0].(iterator.Iterator[SubnetOnlyValidator]) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetActiveSubnetOnlyValidatorsIterator indicates an expected call of GetActiveSubnetOnlyValidatorsIterator. +func (mr *MockDiffMockRecorder) GetActiveSubnetOnlyValidatorsIterator() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveSubnetOnlyValidatorsIterator", reflect.TypeOf((*MockDiff)(nil).GetActiveSubnetOnlyValidatorsIterator)) +} + // GetCurrentDelegatorIterator mocks base method. func (m *MockDiff) GetCurrentDelegatorIterator(subnetID ids.ID, nodeID ids.NodeID) (iterator.Iterator[*Staker], error) { m.ctrl.T.Helper() @@ -383,6 +398,21 @@ func (mr *MockDiffMockRecorder) GetSubnetManager(subnetID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetManager", reflect.TypeOf((*MockDiff)(nil).GetSubnetManager), subnetID) } +// GetSubnetOnlyValidator mocks base method. +func (m *MockDiff) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSubnetOnlyValidator", validationID) + ret0, _ := ret[0].(SubnetOnlyValidator) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSubnetOnlyValidator indicates an expected call of GetSubnetOnlyValidator. +func (mr *MockDiffMockRecorder) GetSubnetOnlyValidator(validationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetOnlyValidator", reflect.TypeOf((*MockDiff)(nil).GetSubnetOnlyValidator), validationID) +} + // GetSubnetOwner mocks base method. func (m *MockDiff) GetSubnetOwner(subnetID ids.ID) (fx.Owner, error) { m.ctrl.T.Helper() @@ -473,6 +503,35 @@ func (mr *MockDiffMockRecorder) HasExpiry(arg0 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasExpiry", reflect.TypeOf((*MockDiff)(nil).HasExpiry), arg0) } +// HasSubnetOnlyValidator mocks base method. +func (m *MockDiff) HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasSubnetOnlyValidator", subnetID, nodeID) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasSubnetOnlyValidator indicates an expected call of HasSubnetOnlyValidator. +func (mr *MockDiffMockRecorder) HasSubnetOnlyValidator(subnetID, nodeID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasSubnetOnlyValidator", reflect.TypeOf((*MockDiff)(nil).HasSubnetOnlyValidator), subnetID, nodeID) +} + +// NumActiveSubnetOnlyValidators mocks base method. +func (m *MockDiff) NumActiveSubnetOnlyValidators() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NumActiveSubnetOnlyValidators") + ret0, _ := ret[0].(int) + return ret0 +} + +// NumActiveSubnetOnlyValidators indicates an expected call of NumActiveSubnetOnlyValidators. +func (mr *MockDiffMockRecorder) NumActiveSubnetOnlyValidators() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumActiveSubnetOnlyValidators", reflect.TypeOf((*MockDiff)(nil).NumActiveSubnetOnlyValidators)) +} + // PutCurrentDelegator mocks base method. func (m *MockDiff) PutCurrentDelegator(staker *Staker) { m.ctrl.T.Helper() @@ -537,6 +596,20 @@ func (mr *MockDiffMockRecorder) PutPendingValidator(staker any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutPendingValidator", reflect.TypeOf((*MockDiff)(nil).PutPendingValidator), staker) } +// PutSubnetOnlyValidator mocks base method. +func (m *MockDiff) PutSubnetOnlyValidator(sov SubnetOnlyValidator) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PutSubnetOnlyValidator", sov) + ret0, _ := ret[0].(error) + return ret0 +} + +// PutSubnetOnlyValidator indicates an expected call of PutSubnetOnlyValidator. +func (mr *MockDiffMockRecorder) PutSubnetOnlyValidator(sov any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutSubnetOnlyValidator", reflect.TypeOf((*MockDiff)(nil).PutSubnetOnlyValidator), sov) +} + // SetAccruedFees mocks base method. func (m *MockDiff) SetAccruedFees(f uint64) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index 2f8ddaa4bc85..8db5f9bec48e 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -319,6 +319,21 @@ func (mr *MockStateMockRecorder) GetAccruedFees() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccruedFees", reflect.TypeOf((*MockState)(nil).GetAccruedFees)) } +// GetActiveSubnetOnlyValidatorsIterator mocks base method. +func (m *MockState) GetActiveSubnetOnlyValidatorsIterator() (iterator.Iterator[SubnetOnlyValidator], error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetActiveSubnetOnlyValidatorsIterator") + ret0, _ := ret[0].(iterator.Iterator[SubnetOnlyValidator]) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetActiveSubnetOnlyValidatorsIterator indicates an expected call of GetActiveSubnetOnlyValidatorsIterator. +func (mr *MockStateMockRecorder) GetActiveSubnetOnlyValidatorsIterator() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveSubnetOnlyValidatorsIterator", reflect.TypeOf((*MockState)(nil).GetActiveSubnetOnlyValidatorsIterator)) +} + // GetBlockIDAtHeight mocks base method. func (m *MockState) GetBlockIDAtHeight(height uint64) (ids.ID, error) { m.ctrl.T.Helper() @@ -588,6 +603,21 @@ func (mr *MockStateMockRecorder) GetSubnetManager(subnetID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetManager", reflect.TypeOf((*MockState)(nil).GetSubnetManager), subnetID) } +// GetSubnetOnlyValidator mocks base method. +func (m *MockState) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSubnetOnlyValidator", validationID) + ret0, _ := ret[0].(SubnetOnlyValidator) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSubnetOnlyValidator indicates an expected call of GetSubnetOnlyValidator. +func (mr *MockStateMockRecorder) GetSubnetOnlyValidator(validationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetOnlyValidator", reflect.TypeOf((*MockState)(nil).GetSubnetOnlyValidator), validationID) +} + // GetSubnetOwner mocks base method. func (m *MockState) GetSubnetOwner(subnetID ids.ID) (fx.Owner, error) { m.ctrl.T.Helper() @@ -694,6 +724,35 @@ func (mr *MockStateMockRecorder) HasExpiry(arg0 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasExpiry", reflect.TypeOf((*MockState)(nil).HasExpiry), arg0) } +// HasSubnetOnlyValidator mocks base method. +func (m *MockState) HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasSubnetOnlyValidator", subnetID, nodeID) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasSubnetOnlyValidator indicates an expected call of HasSubnetOnlyValidator. +func (mr *MockStateMockRecorder) HasSubnetOnlyValidator(subnetID, nodeID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasSubnetOnlyValidator", reflect.TypeOf((*MockState)(nil).HasSubnetOnlyValidator), subnetID, nodeID) +} + +// NumActiveSubnetOnlyValidators mocks base method. +func (m *MockState) NumActiveSubnetOnlyValidators() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NumActiveSubnetOnlyValidators") + ret0, _ := ret[0].(int) + return ret0 +} + +// NumActiveSubnetOnlyValidators indicates an expected call of NumActiveSubnetOnlyValidators. +func (mr *MockStateMockRecorder) NumActiveSubnetOnlyValidators() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumActiveSubnetOnlyValidators", reflect.TypeOf((*MockState)(nil).NumActiveSubnetOnlyValidators)) +} + // PutCurrentDelegator mocks base method. func (m *MockState) PutCurrentDelegator(staker *Staker) { m.ctrl.T.Helper() @@ -758,6 +817,20 @@ func (mr *MockStateMockRecorder) PutPendingValidator(staker any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutPendingValidator", reflect.TypeOf((*MockState)(nil).PutPendingValidator), staker) } +// PutSubnetOnlyValidator mocks base method. +func (m *MockState) PutSubnetOnlyValidator(sov SubnetOnlyValidator) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PutSubnetOnlyValidator", sov) + ret0, _ := ret[0].(error) + return ret0 +} + +// PutSubnetOnlyValidator indicates an expected call of PutSubnetOnlyValidator. +func (mr *MockStateMockRecorder) PutSubnetOnlyValidator(sov any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutSubnetOnlyValidator", reflect.TypeOf((*MockState)(nil).PutSubnetOnlyValidator), sov) +} + // ReindexBlocks mocks base method. func (m *MockState) ReindexBlocks(lock sync.Locker, log logging.Logger) error { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index b6961bf3f271..dfe77e87f97b 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -83,6 +83,10 @@ var ( SupplyPrefix = []byte("supply") ChainPrefix = []byte("chain") ExpiryReplayProtectionPrefix = []byte("expiryReplayProtection") + SubnetOnlyValidatorsPrefix = []byte("subnetOnlyValidators") + SubnetIDNodeIDPrefix = []byte("subnetIDNodeID") + ActivePrefix = []byte("active") + InactivePrefix = []byte("inactive") SingletonPrefix = []byte("singleton") TimestampKey = []byte("timestamp") @@ -308,6 +312,14 @@ type state struct { expiryDiff *expiryDiff expiryDB database.Database + activeSOVLookup map[ids.ID]SubnetOnlyValidator + activeSOVs *btree.BTreeG[SubnetOnlyValidator] + sovDiff *subnetOnlyValidatorsDiff + subnetOnlyValidatorsDB database.Database + subnetIDNodeIDDB database.Database + activeDB database.Database + inactiveDB database.Database + currentStakers *baseStakers pendingStakers *baseStakers @@ -497,6 +509,8 @@ func New( baseDB := versiondb.New(db) + subnetOnlyValidatorsDB := prefixdb.New(SubnetOnlyValidatorsPrefix, baseDB) + validatorsDB := prefixdb.New(ValidatorsPrefix, baseDB) currentValidatorsDB := prefixdb.New(CurrentPrefix, validatorsDB) @@ -623,6 +637,14 @@ func New( expiryDiff: newExpiryDiff(), expiryDB: prefixdb.New(ExpiryReplayProtectionPrefix, baseDB), + activeSOVLookup: make(map[ids.ID]SubnetOnlyValidator), + activeSOVs: btree.NewG(defaultTreeDegree, SubnetOnlyValidator.Less), + sovDiff: newSubnetOnlyValidatorsDiff(), + subnetOnlyValidatorsDB: subnetOnlyValidatorsDB, + subnetIDNodeIDDB: prefixdb.New(SubnetIDNodeIDPrefix, subnetOnlyValidatorsDB), + activeDB: prefixdb.New(ActivePrefix, subnetOnlyValidatorsDB), + inactiveDB: prefixdb.New(InactivePrefix, subnetOnlyValidatorsDB), + currentStakers: newBaseStakers(), pendingStakers: newBaseStakers(), @@ -718,6 +740,57 @@ func (s *state) DeleteExpiry(entry ExpiryEntry) { s.expiryDiff.DeleteExpiry(entry) } +func (s *state) GetActiveSubnetOnlyValidatorsIterator() (iterator.Iterator[SubnetOnlyValidator], error) { + return s.sovDiff.getActiveSubnetOnlyValidatorsIterator( + iterator.FromTree(s.activeSOVs), + ), nil +} + +func (s *state) NumActiveSubnetOnlyValidators() int { + return len(s.activeSOVLookup) + s.sovDiff.numAddedActive +} + +func (s *state) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { + if sov, modified := s.sovDiff.modified[validationID]; modified { + if sov.Weight == 0 { + return SubnetOnlyValidator{}, database.ErrNotFound + } + return sov, nil + } + + if sov, ok := s.activeSOVLookup[validationID]; ok { + return sov, nil + } + + // TODO: Add caching + sovBytes, err := s.inactiveDB.Get(validationID[:]) + if err != nil { + return SubnetOnlyValidator{}, err + } + + var sov SubnetOnlyValidator + if _, err := block.GenesisCodec.Unmarshal(sovBytes, &sov); err != nil { + return SubnetOnlyValidator{}, err + } + return sov, nil +} + +func (s *state) HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool, error) { + if has, modified := s.sovDiff.hasSubnetOnlyValidator(subnetID, nodeID); modified { + return has, nil + } + + // TODO: Add caching + key := make([]byte, len(subnetID)+len(nodeID)) + copy(key, subnetID[:]) + copy(key[len(subnetID):], nodeID[:]) + return s.subnetIDNodeIDDB.Has(key) +} + +func (s *state) PutSubnetOnlyValidator(sov SubnetOnlyValidator) error { + return s.sovDiff.putSubnetOnlyValidator(s, sov) +} + func (s *state) GetCurrentValidator(subnetID ids.ID, nodeID ids.NodeID) (*Staker, error) { return s.currentStakers.GetValidator(subnetID, nodeID) } @@ -1371,6 +1444,7 @@ func (s *state) load() error { return errors.Join( s.loadMetadata(), s.loadExpiry(), + s.loadActiveSubnetOnlyValidators(), s.loadCurrentValidators(), s.loadPendingValidators(), s.initValidatorSets(), @@ -1459,6 +1533,34 @@ func (s *state) loadExpiry() error { return nil } +func (s *state) loadActiveSubnetOnlyValidators() error { + it := s.activeDB.NewIterator() + defer it.Release() + + for it.Next() { + key := it.Key() + validationID, err := ids.ToID(key) + if err != nil { + return fmt.Errorf("failed to unmarshal ValidationID during load: %w", err) + } + + var ( + value = it.Value() + sov = SubnetOnlyValidator{ + ValidationID: validationID, + } + ) + if _, err := block.GenesisCodec.Unmarshal(value, &sov); err != nil { + return fmt.Errorf("failed to unmarshal SubnetOnlyValidator: %w", err) + } + + s.activeSOVLookup[validationID] = sov + s.activeSOVs.ReplaceOrInsert(sov) + } + + return nil +} + func (s *state) loadCurrentValidators() error { s.currentStakers = newBaseStakers() @@ -1758,6 +1860,7 @@ func (s *state) write(updateValidators bool, height uint64) error { return errors.Join( s.writeBlocks(), s.writeExpiry(), + s.writeSubnetOnlyValidators(), s.writeCurrentStakers(updateValidators, height, codecVersion), s.writePendingStakers(), s.WriteValidatorMetadata(s.currentValidatorList, s.currentSubnetValidatorList, codecVersion), // Must be called after writeCurrentStakers @@ -2005,6 +2108,75 @@ func (s *state) writeExpiry() error { return nil } +// TODO: Write weight and public key diffs +// TODO: Add caching +func (s *state) writeSubnetOnlyValidators() error { + // Perform deletions: + for validationID, sov := range s.sovDiff.modified { + if sov.Weight != 0 { + continue + } + + subnetIDNodeIDKey := make([]byte, len(sov.SubnetID)+len(sov.NodeID)) + copy(subnetIDNodeIDKey, sov.SubnetID[:]) + copy(subnetIDNodeIDKey[len(sov.SubnetID):], sov.NodeID[:]) + if err := s.subnetIDNodeIDDB.Delete(subnetIDNodeIDKey); err != nil { + return err + } + + var err error + if priorSOV, ok := s.activeSOVLookup[validationID]; ok { + delete(s.activeSOVLookup, validationID) + s.activeSOVs.Delete(priorSOV) + err = s.activeDB.Delete(validationID[:]) + } else { + err = s.inactiveDB.Delete(validationID[:]) + } + if err != nil { + return err + } + } + // Perform additions/modifications: + for validationID, sov := range s.sovDiff.modified { + if sov.Weight == 0 { + continue + } + + subnetIDNodeIDKey := make([]byte, len(sov.SubnetID)+len(sov.NodeID)) + copy(subnetIDNodeIDKey, sov.SubnetID[:]) + copy(subnetIDNodeIDKey[len(sov.SubnetID):], sov.NodeID[:]) + if err := s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, nil); err != nil { + return err + } + + var err error + if priorSOV, ok := s.activeSOVLookup[validationID]; ok { + delete(s.activeSOVLookup, validationID) + s.activeSOVs.Delete(priorSOV) + err = s.activeDB.Delete(validationID[:]) + } else { + err = s.inactiveDB.Delete(validationID[:]) + } + if err != nil { + return err + } + + if sov.isActive() { + s.activeSOVLookup[validationID] = sov + s.activeSOVs.ReplaceOrInsert(sov) + err = putSubnetOnlyValidator(s.activeDB, sov) + } else { + err = putSubnetOnlyValidator(s.inactiveDB, sov) + } + if err != nil { + return err + } + } + + s.sovDiff = newSubnetOnlyValidatorsDiff() + return nil +} + func (s *state) writeCurrentStakers(updateValidators bool, height uint64, codecVersion uint16) error { for subnetID, validatorDiffs := range s.currentStakers.validatorDiffs { delete(s.currentStakers.validatorDiffs, subnetID) diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index cc30ca29f522..87f9ddacb013 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -30,7 +30,7 @@ type SubnetOnlyValidators interface { // NumActiveSubnetOnlyValidators returns the number of currently active // subnet only validators. - NumActiveSubnetOnlyValidators() (int, error) + NumActiveSubnetOnlyValidators() int // GetSubnetOnlyValidator returns the validator with [validationID] if it // exists. If the validator does not exist, [err] will equal @@ -139,10 +139,10 @@ func getSubnetOnlyValidator(db database.KeyValueReader, validationID ids.ID) (Su vdr := SubnetOnlyValidator{ ValidationID: validationID, } - if _, err = block.GenesisCodec.Unmarshal(bytes, &vdr); err != nil { + if _, err := block.GenesisCodec.Unmarshal(bytes, &vdr); err != nil { return SubnetOnlyValidator{}, fmt.Errorf("failed to unmarshal SubnetOnlyValidator: %w", err) } - return vdr, err + return vdr, nil } func putSubnetOnlyValidator(db database.KeyValueWriter, vdr SubnetOnlyValidator) error { From 09faa6bb8bbf0c6e0d0c59f554553dfbc5570e51 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 13 Sep 2024 23:20:24 -0400 Subject: [PATCH 036/199] nit --- vms/platformvm/state/state.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index dfe77e87f97b..95d0a04a70ff 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -763,16 +763,7 @@ func (s *state) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator } // TODO: Add caching - sovBytes, err := s.inactiveDB.Get(validationID[:]) - if err != nil { - return SubnetOnlyValidator{}, err - } - - var sov SubnetOnlyValidator - if _, err := block.GenesisCodec.Unmarshal(sovBytes, &sov); err != nil { - return SubnetOnlyValidator{}, err - } - return sov, nil + return getSubnetOnlyValidator(s.inactiveDB, validationID) } func (s *state) HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool, error) { From 51937f7d1e1f54f918e4357fc603fe1d2949c5f3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 13 Sep 2024 23:42:36 -0400 Subject: [PATCH 037/199] nit --- vms/platformvm/state/state.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 95d0a04a70ff..2a073b01b852 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1871,6 +1871,10 @@ func (s *state) write(updateValidators bool, height uint64) error { func (s *state) Close() error { return errors.Join( s.expiryDB.Close(), + s.subnetIDNodeIDDB.Close(), + s.activeDB.Close(), + s.inactiveDB.Close(), + s.subnetOnlyValidatorsDB.Close(), s.pendingSubnetValidatorBaseDB.Close(), s.pendingSubnetDelegatorBaseDB.Close(), s.pendingDelegatorBaseDB.Close(), @@ -2119,9 +2123,9 @@ func (s *state) writeSubnetOnlyValidators() error { if priorSOV, ok := s.activeSOVLookup[validationID]; ok { delete(s.activeSOVLookup, validationID) s.activeSOVs.Delete(priorSOV) - err = s.activeDB.Delete(validationID[:]) + err = deleteSubnetOnlyValidator(s.activeDB, validationID) } else { - err = s.inactiveDB.Delete(validationID[:]) + err = deleteSubnetOnlyValidator(s.inactiveDB, validationID) } if err != nil { return err @@ -2144,9 +2148,9 @@ func (s *state) writeSubnetOnlyValidators() error { if priorSOV, ok := s.activeSOVLookup[validationID]; ok { delete(s.activeSOVLookup, validationID) s.activeSOVs.Delete(priorSOV) - err = s.activeDB.Delete(validationID[:]) + err = deleteSubnetOnlyValidator(s.activeDB, validationID) } else { - err = s.inactiveDB.Delete(validationID[:]) + err = deleteSubnetOnlyValidator(s.inactiveDB, validationID) } if err != nil { return err From 88a310e027640b76e95a42755124c0c9f826c04e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 14 Sep 2024 22:25:55 -0400 Subject: [PATCH 038/199] Add num SoV validators on a subnet --- vms/platformvm/state/diff.go | 13 ++ vms/platformvm/state/diff_test.go | 13 +- vms/platformvm/state/mock_chain.go | 15 +++ vms/platformvm/state/mock_diff.go | 15 +++ vms/platformvm/state/mock_state.go | 15 +++ vms/platformvm/state/state.go | 32 +++++ vms/platformvm/state/subnet_only_validator.go | 111 ++++++++++-------- 7 files changed, 167 insertions(+), 47 deletions(-) diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index deb09026282d..1e51717c8d14 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -206,6 +206,19 @@ func (d *diff) NumActiveSubnetOnlyValidators() int { return d.parentActiveSOVs + d.sovDiff.numAddedActive } +func (d *diff) NumSubnetOnlyValidators(subnetID ids.ID) (int, error) { + if numSOVs, modified := d.sovDiff.modifiedNumValidators[subnetID]; modified { + return numSOVs, nil + } + + parentState, ok := d.stateVersions.GetState(d.parentID) + if !ok { + return 0, fmt.Errorf("%w: %s", ErrMissingParentState, d.parentID) + } + + return parentState.NumSubnetOnlyValidators(subnetID) +} + func (d *diff) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { if sov, modified := d.sovDiff.modified[validationID]; modified { if sov.Weight == 0 { diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index a34a5b40c8f2..e33dc33e0e20 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -513,7 +513,10 @@ func TestDiffSubnetOnlyValidators(t *testing.T) { require.Zero(sov) } - var expectedActive []SubnetOnlyValidator + var ( + numSOVs = make(map[ids.ID]int) + expectedActive []SubnetOnlyValidator + ) for _, expectedSOV := range expectedSOVs { if expectedSOV.Weight == 0 { continue @@ -527,6 +530,7 @@ func TestDiffSubnetOnlyValidators(t *testing.T) { require.NoError(err) require.True(has) + numSOVs[sov.SubnetID]++ if expectedSOV.isActive() { expectedActive = append(expectedActive, expectedSOV) } @@ -541,10 +545,17 @@ func TestDiffSubnetOnlyValidators(t *testing.T) { ) require.Equal(len(expectedActive), chain.NumActiveSubnetOnlyValidators()) + + for subnetID, expectedNumSOVs := range numSOVs { + numSOVs, err := chain.NumSubnetOnlyValidators(subnetID) + require.NoError(err) + require.Equal(expectedNumSOVs, numSOVs) + } } verifyChain(d) require.NoError(d.Apply(state)) + verifyChain(d) verifyChain(state) assertChainsEqual(t, state, d) }) diff --git a/vms/platformvm/state/mock_chain.go b/vms/platformvm/state/mock_chain.go index 2ce5a181a684..835389f12d79 100644 --- a/vms/platformvm/state/mock_chain.go +++ b/vms/platformvm/state/mock_chain.go @@ -518,6 +518,21 @@ func (mr *MockChainMockRecorder) NumActiveSubnetOnlyValidators() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumActiveSubnetOnlyValidators", reflect.TypeOf((*MockChain)(nil).NumActiveSubnetOnlyValidators)) } +// NumSubnetOnlyValidators mocks base method. +func (m *MockChain) NumSubnetOnlyValidators(subnetID ids.ID) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NumSubnetOnlyValidators", subnetID) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NumSubnetOnlyValidators indicates an expected call of NumSubnetOnlyValidators. +func (mr *MockChainMockRecorder) NumSubnetOnlyValidators(subnetID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumSubnetOnlyValidators", reflect.TypeOf((*MockChain)(nil).NumSubnetOnlyValidators), subnetID) +} + // PutCurrentDelegator mocks base method. func (m *MockChain) PutCurrentDelegator(staker *Staker) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/mock_diff.go b/vms/platformvm/state/mock_diff.go index c2138705dc66..8c9e48d7845d 100644 --- a/vms/platformvm/state/mock_diff.go +++ b/vms/platformvm/state/mock_diff.go @@ -532,6 +532,21 @@ func (mr *MockDiffMockRecorder) NumActiveSubnetOnlyValidators() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumActiveSubnetOnlyValidators", reflect.TypeOf((*MockDiff)(nil).NumActiveSubnetOnlyValidators)) } +// NumSubnetOnlyValidators mocks base method. +func (m *MockDiff) NumSubnetOnlyValidators(subnetID ids.ID) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NumSubnetOnlyValidators", subnetID) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NumSubnetOnlyValidators indicates an expected call of NumSubnetOnlyValidators. +func (mr *MockDiffMockRecorder) NumSubnetOnlyValidators(subnetID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumSubnetOnlyValidators", reflect.TypeOf((*MockDiff)(nil).NumSubnetOnlyValidators), subnetID) +} + // PutCurrentDelegator mocks base method. func (m *MockDiff) PutCurrentDelegator(staker *Staker) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index 8db5f9bec48e..2adfe08eaf3c 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -753,6 +753,21 @@ func (mr *MockStateMockRecorder) NumActiveSubnetOnlyValidators() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumActiveSubnetOnlyValidators", reflect.TypeOf((*MockState)(nil).NumActiveSubnetOnlyValidators)) } +// NumSubnetOnlyValidators mocks base method. +func (m *MockState) NumSubnetOnlyValidators(subnetID ids.ID) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NumSubnetOnlyValidators", subnetID) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NumSubnetOnlyValidators indicates an expected call of NumSubnetOnlyValidators. +func (mr *MockStateMockRecorder) NumSubnetOnlyValidators(subnetID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumSubnetOnlyValidators", reflect.TypeOf((*MockState)(nil).NumSubnetOnlyValidators), subnetID) +} + // PutCurrentDelegator mocks base method. func (m *MockState) PutCurrentDelegator(staker *Staker) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 2a073b01b852..7d3754286261 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -14,6 +14,7 @@ import ( "github.com/google/btree" "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" + "golang.org/x/exp/maps" "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/cache/metercacher" @@ -84,6 +85,7 @@ var ( ChainPrefix = []byte("chain") ExpiryReplayProtectionPrefix = []byte("expiryReplayProtection") SubnetOnlyValidatorsPrefix = []byte("subnetOnlyValidators") + NumValidatorsPrefix = []byte("numValidators") SubnetIDNodeIDPrefix = []byte("subnetIDNodeID") ActivePrefix = []byte("active") InactivePrefix = []byte("inactive") @@ -316,6 +318,7 @@ type state struct { activeSOVs *btree.BTreeG[SubnetOnlyValidator] sovDiff *subnetOnlyValidatorsDiff subnetOnlyValidatorsDB database.Database + numValidatorsDB database.Database subnetIDNodeIDDB database.Database activeDB database.Database inactiveDB database.Database @@ -641,6 +644,7 @@ func New( activeSOVs: btree.NewG(defaultTreeDegree, SubnetOnlyValidator.Less), sovDiff: newSubnetOnlyValidatorsDiff(), subnetOnlyValidatorsDB: subnetOnlyValidatorsDB, + numValidatorsDB: prefixdb.New(NumValidatorsPrefix, subnetOnlyValidatorsDB), subnetIDNodeIDDB: prefixdb.New(SubnetIDNodeIDPrefix, subnetOnlyValidatorsDB), activeDB: prefixdb.New(ActivePrefix, subnetOnlyValidatorsDB), inactiveDB: prefixdb.New(InactivePrefix, subnetOnlyValidatorsDB), @@ -750,6 +754,25 @@ func (s *state) NumActiveSubnetOnlyValidators() int { return len(s.activeSOVLookup) + s.sovDiff.numAddedActive } +func (s *state) NumSubnetOnlyValidators(subnetID ids.ID) (int, error) { + if numSOVs, modified := s.sovDiff.modifiedNumValidators[subnetID]; modified { + return numSOVs, nil + } + + // TODO: Add caching + numSOVs, err := database.GetUInt64(s.numValidatorsDB, subnetID[:]) + if err == database.ErrNotFound { + return 0, nil + } + if err != nil { + return 0, err + } + if numSOVs > math.MaxInt { + return 0, safemath.ErrOverflow + } + return int(numSOVs), nil +} + func (s *state) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { if sov, modified := s.sovDiff.modified[validationID]; modified { if sov.Weight == 0 { @@ -1871,6 +1894,7 @@ func (s *state) write(updateValidators bool, height uint64) error { func (s *state) Close() error { return errors.Join( s.expiryDB.Close(), + s.numValidatorsDB.Close(), s.subnetIDNodeIDDB.Close(), s.activeDB.Close(), s.inactiveDB.Close(), @@ -2106,6 +2130,14 @@ func (s *state) writeExpiry() error { // TODO: Write weight and public key diffs // TODO: Add caching func (s *state) writeSubnetOnlyValidators() error { + // Write counts: + for subnetID, numValidators := range s.sovDiff.modifiedNumValidators { + if err := database.PutUInt64(s.numValidatorsDB, subnetID[:], uint64(numValidators)); err != nil { + return err + } + } + maps.Clear(s.sovDiff.modifiedNumValidators) + // Perform deletions: for validationID, sov := range s.sovDiff.modified { if sov.Weight != 0 { diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 87f9ddacb013..7463e9b72fc6 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -32,14 +32,17 @@ type SubnetOnlyValidators interface { // subnet only validators. NumActiveSubnetOnlyValidators() int + // NumSubnetOnlyValidators returns the total number of subnet only + // validators on [subnetID]. + NumSubnetOnlyValidators(subnetID ids.ID) (int, error) + // GetSubnetOnlyValidator returns the validator with [validationID] if it // exists. If the validator does not exist, [err] will equal // [database.ErrNotFound]. GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) // HasSubnetOnlyValidator returns the validator with [validationID] if it - // exists. If the validator does not exist, [err] will equal - // [database.ErrNotFound]. + // exists. HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool, error) // PutSubnetOnlyValidator inserts [sov] as a validator. @@ -163,17 +166,19 @@ type subnetIDNodeID struct { } type subnetOnlyValidatorsDiff struct { - numAddedActive int // May be negative - modified map[ids.ID]SubnetOnlyValidator - modifiedHasNodeIDs map[subnetIDNodeID]bool - active *btree.BTreeG[SubnetOnlyValidator] + numAddedActive int // May be negative + modifiedNumValidators map[ids.ID]int // subnetID -> numValidators + modified map[ids.ID]SubnetOnlyValidator + modifiedHasNodeIDs map[subnetIDNodeID]bool + active *btree.BTreeG[SubnetOnlyValidator] } func newSubnetOnlyValidatorsDiff() *subnetOnlyValidatorsDiff { return &subnetOnlyValidatorsDiff{ - modified: make(map[ids.ID]SubnetOnlyValidator), - modifiedHasNodeIDs: make(map[subnetIDNodeID]bool), - active: btree.NewG(defaultTreeDegree, SubnetOnlyValidator.Less), + modifiedNumValidators: make(map[ids.ID]int), + modified: make(map[ids.ID]SubnetOnlyValidator), + modifiedHasNodeIDs: make(map[subnetIDNodeID]bool), + active: btree.NewG(defaultTreeDegree, SubnetOnlyValidator.Less), } } @@ -198,11 +203,59 @@ func (d *subnetOnlyValidatorsDiff) hasSubnetOnlyValidator(subnetID ids.ID, nodeI } func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state SubnetOnlyValidators, sov SubnetOnlyValidator) error { - diff, err := numActiveSubnetOnlyValidatorChange(state, sov) - if err != nil { + var ( + prevExists bool + prevActive bool + newExists = sov.Weight != 0 + newActive = newExists && sov.EndAccumulatedFee != 0 + ) + switch priorSOV, err := state.GetSubnetOnlyValidator(sov.ValidationID); err { + case nil: + if !priorSOV.validateConstants(sov) { + return ErrMutatedSubnetOnlyValidator + } + + prevExists = true + prevActive = priorSOV.EndAccumulatedFee != 0 + case database.ErrNotFound: + if !newExists { + return nil // Removing a validator that didn't exist is a noop + } + + has, err := state.HasSubnetOnlyValidator(sov.SubnetID, sov.NodeID) + if err != nil { + return err + } + if has { + return ErrDuplicateSubnetOnlyValidator + } + default: return err } - d.numAddedActive += diff + + switch { + case prevExists && !newExists: + numSOVs, err := state.NumSubnetOnlyValidators(sov.SubnetID) + if err != nil { + return err + } + + d.modifiedNumValidators[sov.SubnetID] = numSOVs - 1 + case !prevExists && newExists: + numSOVs, err := state.NumSubnetOnlyValidators(sov.SubnetID) + if err != nil { + return err + } + + d.modifiedNumValidators[sov.SubnetID] = numSOVs + 1 + } + + switch { + case prevActive && !newActive: + d.numAddedActive-- + case !prevActive && newActive: + d.numAddedActive++ + } if prevSOV, ok := d.modified[sov.ValidationID]; ok { prevSubnetIDNodeID := subnetIDNodeID{ @@ -227,37 +280,3 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state SubnetOnlyValida d.active.ReplaceOrInsert(sov) return nil } - -// numActiveSubnetOnlyValidatorChange returns the change in the number of active -// subnet only validators if [sov] were to be inserted into [state]. If it is -// invalid for [sov] to be inserted, an error is returned. -func numActiveSubnetOnlyValidatorChange(state SubnetOnlyValidators, sov SubnetOnlyValidator) (int, error) { - switch priorSOV, err := state.GetSubnetOnlyValidator(sov.ValidationID); err { - case nil: - if !priorSOV.validateConstants(sov) { - return 0, ErrMutatedSubnetOnlyValidator - } - switch { - case !priorSOV.isActive() && sov.isActive(): - return 1, nil // Increasing the number of active validators - case priorSOV.isActive() && !sov.isActive(): - return -1, nil // Decreasing the number of active validators - default: - return 0, nil - } - case database.ErrNotFound: - has, err := state.HasSubnetOnlyValidator(sov.SubnetID, sov.NodeID) - if err != nil { - return 0, err - } - if has { - return 0, ErrDuplicateSubnetOnlyValidator - } - if sov.isActive() { - return 1, nil // Increasing the number of active validators - } - return 0, nil // Adding an inactive validator - default: - return 0, err - } -} From b11ff7e40d21d68d3dd74d45b5158c2bc91e8df5 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 15 Sep 2024 10:42:34 -0400 Subject: [PATCH 039/199] fix tests --- vms/platformvm/block/executor/proposal_block_test.go | 2 ++ vms/platformvm/block/executor/standard_block_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/vms/platformvm/block/executor/proposal_block_test.go b/vms/platformvm/block/executor/proposal_block_test.go index 66a6c7604e23..0708704ebabf 100644 --- a/vms/platformvm/block/executor/proposal_block_test.go +++ b/vms/platformvm/block/executor/proposal_block_test.go @@ -91,6 +91,7 @@ func TestApricotProposalBlockTimeVerification(t *testing.T) { onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() + onParentAccept.EXPECT().NumActiveSubnetOnlyValidators().Return(0).AnyTimes() currentStakersIt := iteratormock.NewIterator[*state.Staker](ctrl) currentStakersIt.EXPECT().Next().Return(true) @@ -163,6 +164,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { onParentAccept.EXPECT().GetTimestamp().Return(parentTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() + onParentAccept.EXPECT().NumActiveSubnetOnlyValidators().Return(0).AnyTimes() onParentAccept.EXPECT().GetCurrentSupply(constants.PrimaryNetworkID).Return(uint64(1000), nil).AnyTimes() env.blkManager.(*manager).blkIDToState[parentID] = &blockState{ diff --git a/vms/platformvm/block/executor/standard_block_test.go b/vms/platformvm/block/executor/standard_block_test.go index fa64eee74697..1bdd822144f3 100644 --- a/vms/platformvm/block/executor/standard_block_test.go +++ b/vms/platformvm/block/executor/standard_block_test.go @@ -60,6 +60,7 @@ func TestApricotStandardBlockTimeVerification(t *testing.T) { onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() + onParentAccept.EXPECT().NumActiveSubnetOnlyValidators().Return(0).AnyTimes() // wrong height apricotChildBlk, err := block.NewApricotStandardBlock( @@ -138,6 +139,7 @@ func TestBanffStandardBlockTimeVerification(t *testing.T) { onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() + onParentAccept.EXPECT().NumActiveSubnetOnlyValidators().Return(0).AnyTimes() txID := ids.GenerateTestID() utxo := &avax.UTXO{ From b028adab408e4e8f8e158d0a4e4f9590aef71a0a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 15 Sep 2024 14:07:45 -0400 Subject: [PATCH 040/199] write historical diffs --- vms/platformvm/state/state.go | 124 ++++++++++++++++++++++++++++++++-- 1 file changed, 119 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 7d3754286261..4ad67970881b 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -4,6 +4,7 @@ package state import ( + "bytes" "context" "errors" "fmt" @@ -781,6 +782,10 @@ func (s *state) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator return sov, nil } + return s.getPersistedSubnetOnlyValidator(validationID) +} + +func (s *state) getPersistedSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { if sov, ok := s.activeSOVLookup[validationID]; ok { return sov, nil } @@ -1874,7 +1879,7 @@ func (s *state) write(updateValidators bool, height uint64) error { return errors.Join( s.writeBlocks(), s.writeExpiry(), - s.writeSubnetOnlyValidators(), + s.writeSubnetOnlyValidators(height), s.writeCurrentStakers(updateValidators, height, codecVersion), s.writePendingStakers(), s.WriteValidatorMetadata(s.currentValidatorList, s.currentSubnetValidatorList, codecVersion), // Must be called after writeCurrentStakers @@ -2127,9 +2132,9 @@ func (s *state) writeExpiry() error { return nil } -// TODO: Write weight and public key diffs +// TODO: Update validator sets // TODO: Add caching -func (s *state) writeSubnetOnlyValidators() error { +func (s *state) writeSubnetOnlyValidators(height uint64) error { // Write counts: for subnetID, numValidators := range s.sovDiff.modifiedNumValidators { if err := database.PutUInt64(s.numValidatorsDB, subnetID[:], uint64(numValidators)); err != nil { @@ -2138,6 +2143,32 @@ func (s *state) writeSubnetOnlyValidators() error { } maps.Clear(s.sovDiff.modifiedNumValidators) + historicalDiffs, err := s.makeSubnetOnlyValidatorHistoricalDiffs() + if err != nil { + return err + } + for subnetIDNodeID, diff := range historicalDiffs { + diffKey := marshalDiffKey(subnetIDNodeID.subnetID, height, subnetIDNodeID.nodeID) + if diff.weightDiff.Amount != 0 { + err := s.validatorWeightDiffsDB.Put( + diffKey, + marshalWeightDiff(&diff.weightDiff), + ) + if err != nil { + return err + } + } + if !bytes.Equal(diff.prevPublicKey, diff.newPublicKey) { + err := s.validatorPublicKeyDiffsDB.Put( + diffKey, + diff.prevPublicKey, + ) + if err != nil { + return err + } + } + } + // Perform deletions: for validationID, sov := range s.sovDiff.modified { if sov.Weight != 0 { @@ -2151,8 +2182,12 @@ func (s *state) writeSubnetOnlyValidators() error { return err } - var err error - if priorSOV, ok := s.activeSOVLookup[validationID]; ok { + priorSOV, err := s.getPersistedSubnetOnlyValidator(validationID) + if err != nil { + return err + } + + if priorSOV.isActive() { delete(s.activeSOVLookup, validationID) s.activeSOVs.Delete(priorSOV) err = deleteSubnetOnlyValidator(s.activeDB, validationID) @@ -2204,6 +2239,85 @@ func (s *state) writeSubnetOnlyValidators() error { return nil } +type validatorChanges struct { + weightDiff ValidatorWeightDiff + prevPublicKey []byte + newPublicKey []byte +} + +func getOrDefault[K comparable, V any](m map[K]*V, k K) *V { + if v, ok := m[k]; ok { + return v + } + + v := new(V) + m[k] = v + return v +} + +func (s *state) makeSubnetOnlyValidatorHistoricalDiffs() (map[subnetIDNodeID]*validatorChanges, error) { + changes := make(map[subnetIDNodeID]*validatorChanges, len(s.sovDiff.modified)) + + // Perform deletions: + for validationID := range s.sovDiff.modified { + priorSOV, err := s.getPersistedSubnetOnlyValidator(validationID) + if err == database.ErrNotFound { + continue + } + if err != nil { + return nil, err + } + + var ( + diff *validatorChanges + subnetIDNodeID = subnetIDNodeID{ + subnetID: priorSOV.SubnetID, + } + ) + if priorSOV.isActive() { + subnetIDNodeID.nodeID = priorSOV.NodeID + diff = getOrDefault(changes, subnetIDNodeID) + diff.prevPublicKey = priorSOV.PublicKey + } else { + subnetIDNodeID.nodeID = ids.EmptyNodeID + diff = getOrDefault(changes, subnetIDNodeID) + } + + if err := diff.weightDiff.Add(true, priorSOV.Weight); err != nil { + return nil, err + } + } + + // Perform additions: + for _, sov := range s.sovDiff.modified { + // If the validator is being removed, we shouldn't work to re-add it. + if sov.Weight == 0 { + continue + } + + var ( + diff *validatorChanges + subnetIDNodeID = subnetIDNodeID{ + subnetID: sov.SubnetID, + } + ) + if sov.isActive() { + subnetIDNodeID.nodeID = sov.NodeID + diff = getOrDefault(changes, subnetIDNodeID) + diff.newPublicKey = sov.PublicKey + } else { + subnetIDNodeID.nodeID = ids.EmptyNodeID + diff = getOrDefault(changes, subnetIDNodeID) + } + + if err := diff.weightDiff.Add(false, sov.Weight); err != nil { + return nil, err + } + } + + return changes, nil +} + func (s *state) writeCurrentStakers(updateValidators bool, height uint64, codecVersion uint16) error { for subnetID, validatorDiffs := range s.currentStakers.validatorDiffs { delete(s.currentStakers.validatorDiffs, subnetID) From 42d61efdacabfeded915d7bbdc5afa9472918c0c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 16 Sep 2024 15:54:54 -0400 Subject: [PATCH 041/199] Implement and test state diffs --- vms/platformvm/state/diff_test.go | 296 ------------- vms/platformvm/state/mock_state.go | 8 +- vms/platformvm/state/state.go | 21 +- vms/platformvm/state/state_test.go | 407 ++++++++++++++++++ vms/platformvm/state/subnet_only_validator.go | 4 - vms/platformvm/validators/manager.go | 6 +- 6 files changed, 430 insertions(+), 312 deletions(-) diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index e33dc33e0e20..187667c34e03 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -266,302 +266,6 @@ func TestDiffExpiry(t *testing.T) { } } -func TestDiffSubnetOnlyValidators(t *testing.T) { - sov := SubnetOnlyValidator{ - ValidationID: ids.GenerateTestID(), - SubnetID: ids.GenerateTestID(), - NodeID: ids.GenerateTestNodeID(), - } - - tests := []struct { - name string - initial []SubnetOnlyValidator - sovs []SubnetOnlyValidator - }{ - { - name: "empty noop", - }, - { - name: "initially active not modified", - initial: []SubnetOnlyValidator{ - { - ValidationID: ids.GenerateTestID(), - SubnetID: ids.GenerateTestID(), - NodeID: ids.GenerateTestNodeID(), - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active - }, - }, - }, - { - name: "initially inactive not modified", - initial: []SubnetOnlyValidator{ - { - ValidationID: ids.GenerateTestID(), - SubnetID: ids.GenerateTestID(), - NodeID: ids.GenerateTestNodeID(), - Weight: 1, // Not removed - EndAccumulatedFee: 0, // Inactive - }, - }, - }, - { - name: "initially active removed", - initial: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active - }, - }, - sovs: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 0, // Removed - }, - }, - }, - { - name: "initially inactive removed", - initial: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 1, // Not removed - EndAccumulatedFee: 0, // Inactive - }, - }, - sovs: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 0, // Removed - }, - }, - }, - { - name: "increase active weight", - initial: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active - }, - }, - sovs: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 2, // Increased - EndAccumulatedFee: 1, // Active - }, - }, - }, - { - name: "deactivate", - initial: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active - }, - }, - sovs: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 1, // Not removed - EndAccumulatedFee: 0, // Inactive - }, - }, - }, - { - name: "reactivate", - initial: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 1, // Not removed - EndAccumulatedFee: 0, // Inactive - }, - }, - sovs: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active - }, - }, - }, - { - name: "update multiple times", - initial: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active - }, - }, - sovs: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 2, // Not removed - EndAccumulatedFee: 1, // Inactive - }, - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 3, // Not removed - EndAccumulatedFee: 1, // Inactive - }, - }, - }, - { - name: "change validationID", - initial: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active - }, - }, - sovs: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 0, // Removed - }, - { - ValidationID: ids.GenerateTestID(), - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Inactive - }, - }, - }, - { - name: "added and removed", - sovs: []SubnetOnlyValidator{ - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active - }, - { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - Weight: 0, // Removed - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - state := newTestState(t, memdb.New()) - expectedSOVs := make(map[ids.ID]SubnetOnlyValidator) - for _, sov := range test.initial { - require.NoError(state.PutSubnetOnlyValidator(sov)) - expectedSOVs[sov.ValidationID] = sov - } - - d, err := NewDiffOn(state) - require.NoError(err) - - for _, sov := range test.sovs { - require.NoError(d.PutSubnetOnlyValidator(sov)) - expectedSOVs[sov.ValidationID] = sov - } - - verifyChain := func(chain Chain) { - for _, expectedSOV := range expectedSOVs { - if expectedSOV.Weight != 0 { - continue - } - - sov, err := chain.GetSubnetOnlyValidator(expectedSOV.ValidationID) - require.ErrorIs(err, database.ErrNotFound) - require.Zero(sov) - } - - var ( - numSOVs = make(map[ids.ID]int) - expectedActive []SubnetOnlyValidator - ) - for _, expectedSOV := range expectedSOVs { - if expectedSOV.Weight == 0 { - continue - } - - sov, err := chain.GetSubnetOnlyValidator(expectedSOV.ValidationID) - require.NoError(err) - require.Equal(expectedSOV, sov) - - has, err := chain.HasSubnetOnlyValidator(expectedSOV.SubnetID, expectedSOV.NodeID) - require.NoError(err) - require.True(has) - - numSOVs[sov.SubnetID]++ - if expectedSOV.isActive() { - expectedActive = append(expectedActive, expectedSOV) - } - } - utils.Sort(expectedActive) - - activeIterator, err := chain.GetActiveSubnetOnlyValidatorsIterator() - require.NoError(err) - require.Equal( - expectedActive, - iterator.ToSlice(activeIterator), - ) - - require.Equal(len(expectedActive), chain.NumActiveSubnetOnlyValidators()) - - for subnetID, expectedNumSOVs := range numSOVs { - numSOVs, err := chain.NumSubnetOnlyValidators(subnetID) - require.NoError(err) - require.Equal(expectedNumSOVs, numSOVs) - } - } - - verifyChain(d) - require.NoError(d.Apply(state)) - verifyChain(d) - verifyChain(state) - assertChainsEqual(t, state, d) - }) - } -} - func TestDiffSubnetOnlyValidatorsErrors(t *testing.T) { sov := SubnetOnlyValidator{ ValidationID: ids.GenerateTestID(), diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index 2adfe08eaf3c..0f67d6607ba3 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -149,17 +149,17 @@ func (mr *MockStateMockRecorder) AddUTXO(utxo any) *gomock.Call { } // ApplyValidatorPublicKeyDiffs mocks base method. -func (m *MockState) ApplyValidatorPublicKeyDiffs(ctx context.Context, validators map[ids.NodeID]*validators.GetValidatorOutput, startHeight, endHeight uint64) error { +func (m *MockState) ApplyValidatorPublicKeyDiffs(ctx context.Context, validators map[ids.NodeID]*validators.GetValidatorOutput, startHeight, endHeight uint64, subnetID ids.ID) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ApplyValidatorPublicKeyDiffs", ctx, validators, startHeight, endHeight) + ret := m.ctrl.Call(m, "ApplyValidatorPublicKeyDiffs", ctx, validators, startHeight, endHeight, subnetID) ret0, _ := ret[0].(error) return ret0 } // ApplyValidatorPublicKeyDiffs indicates an expected call of ApplyValidatorPublicKeyDiffs. -func (mr *MockStateMockRecorder) ApplyValidatorPublicKeyDiffs(ctx, validators, startHeight, endHeight any) *gomock.Call { +func (mr *MockStateMockRecorder) ApplyValidatorPublicKeyDiffs(ctx, validators, startHeight, endHeight, subnetID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyValidatorPublicKeyDiffs", reflect.TypeOf((*MockState)(nil).ApplyValidatorPublicKeyDiffs), ctx, validators, startHeight, endHeight) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyValidatorPublicKeyDiffs", reflect.TypeOf((*MockState)(nil).ApplyValidatorPublicKeyDiffs), ctx, validators, startHeight, endHeight, subnetID) } // ApplyValidatorWeightDiffs mocks base method. diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 4ad67970881b..ba4fcb46eb05 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -197,6 +197,7 @@ type State interface { validators map[ids.NodeID]*validators.GetValidatorOutput, startHeight uint64, endHeight uint64, + subnetID ids.ID, ) error SetHeight(height uint64) @@ -1336,10 +1337,11 @@ func (s *state) ApplyValidatorPublicKeyDiffs( validators map[ids.NodeID]*validators.GetValidatorOutput, startHeight uint64, endHeight uint64, + subnetID ids.ID, ) error { diffIter := s.validatorPublicKeyDiffsDB.NewIteratorWithStartAndPrefix( - marshalStartDiffKey(constants.PrimaryNetworkID, startHeight), - constants.PrimaryNetworkID[:], + marshalStartDiffKey(subnetID, startHeight), + subnetID[:], ) defer diffIter.Release() @@ -2175,6 +2177,16 @@ func (s *state) writeSubnetOnlyValidators(height uint64) error { continue } + priorSOV, err := s.getPersistedSubnetOnlyValidator(validationID) + if err == database.ErrNotFound { + // Deleting a non-existent validator is a noop. This can happen if + // the validator was added and then immediately removed. + continue + } + if err != nil { + return err + } + subnetIDNodeIDKey := make([]byte, len(sov.SubnetID)+len(sov.NodeID)) copy(subnetIDNodeIDKey, sov.SubnetID[:]) copy(subnetIDNodeIDKey[len(sov.SubnetID):], sov.NodeID[:]) @@ -2182,11 +2194,6 @@ func (s *state) writeSubnetOnlyValidators(height uint64) error { return err } - priorSOV, err := s.getPersistedSubnetOnlyValidator(validationID) - if err != nil { - return err - } - if priorSOV.isActive() { delete(s.activeSOVLookup, validationID) s.activeSOVs.Delete(priorSOV) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index a0dcd0cb7dcc..0beff373a0bf 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -6,6 +6,7 @@ package state import ( "context" "fmt" + "maps" "math" "math/rand" "sync" @@ -24,10 +25,12 @@ import ( "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/upgrade/upgradetest" + "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -1156,6 +1159,7 @@ func TestStateAddRemoveValidator(t *testing.T) { primaryValidatorSet, currentHeight, prevHeight+1, + constants.PrimaryNetworkID, )) requireEqualPublicKeysValidatorSet(require, prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) @@ -1609,3 +1613,406 @@ func TestStateExpiryCommitAndLoad(t *testing.T) { require.NoError(err) require.False(has) } + +func TestSubnetOnlyValidators(t *testing.T) { + sov := SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + } + + sk, err := bls.NewSecretKey() + require.NoError(t, err) + pk := bls.PublicFromSecretKey(sk) + pkBytes := bls.PublicKeyToUncompressedBytes(pk) + + otherSK, err := bls.NewSecretKey() + require.NoError(t, err) + otherPK := bls.PublicFromSecretKey(otherSK) + otherPKBytes := bls.PublicKeyToUncompressedBytes(otherPK) + + tests := []struct { + name string + initial []SubnetOnlyValidator + sovs []SubnetOnlyValidator + }{ + { + name: "empty noop", + }, + { + name: "initially active not modified", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + }, + { + name: "initially inactive not modified", + initial: []SubnetOnlyValidator{ + { + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 1, // Not removed + EndAccumulatedFee: 0, // Inactive + }, + }, + }, + { + name: "initially active removed", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 0, // Removed + }, + }, + }, + { + name: "initially inactive removed", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 1, // Not removed + EndAccumulatedFee: 0, // Inactive + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 0, // Removed + }, + }, + }, + { + name: "increase active weight", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 2, // Increased + EndAccumulatedFee: 1, // Active + }, + }, + }, + { + name: "deactivate", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 1, // Not removed + EndAccumulatedFee: 0, // Inactive + }, + }, + }, + { + name: "reactivate", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 1, // Not removed + EndAccumulatedFee: 0, // Inactive + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + }, + { + name: "update multiple times", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 2, // Not removed + EndAccumulatedFee: 1, // Inactive + }, + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 3, // Not removed + EndAccumulatedFee: 1, // Inactive + }, + }, + }, + { + name: "change validationID", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 0, // Removed + }, + { + ValidationID: ids.GenerateTestID(), + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: otherPKBytes, + RemainingBalanceOwner: []byte{}, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Inactive + }, + }, + }, + { + name: "added and removed", + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active + }, + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: []byte{}, + Weight: 0, // Removed + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + state := newTestState(t, memdb.New()) + + var ( + initialSOVs = make(map[ids.ID]SubnetOnlyValidator) + subnetIDs set.Set[ids.ID] + ) + for _, sov := range test.initial { + require.NoError(state.PutSubnetOnlyValidator(sov)) + initialSOVs[sov.ValidationID] = sov + subnetIDs.Add(sov.SubnetID) + } + + state.SetHeight(0) + require.NoError(state.Commit()) + + d, err := NewDiffOn(state) + require.NoError(err) + + expectedSOVs := maps.Clone(initialSOVs) + for _, sov := range test.sovs { + require.NoError(d.PutSubnetOnlyValidator(sov)) + expectedSOVs[sov.ValidationID] = sov + subnetIDs.Add(sov.SubnetID) + } + + verifyChain := func(chain Chain) { + for _, expectedSOV := range expectedSOVs { + if expectedSOV.Weight != 0 { + continue + } + + sov, err := chain.GetSubnetOnlyValidator(expectedSOV.ValidationID) + require.ErrorIs(err, database.ErrNotFound) + require.Zero(sov) + } + + var ( + numSOVs = make(map[ids.ID]int) + expectedActive []SubnetOnlyValidator + ) + for _, expectedSOV := range expectedSOVs { + if expectedSOV.Weight == 0 { + continue + } + + sov, err := chain.GetSubnetOnlyValidator(expectedSOV.ValidationID) + require.NoError(err) + require.Equal(expectedSOV, sov) + + has, err := chain.HasSubnetOnlyValidator(expectedSOV.SubnetID, expectedSOV.NodeID) + require.NoError(err) + require.True(has) + + numSOVs[sov.SubnetID]++ + if expectedSOV.isActive() { + expectedActive = append(expectedActive, expectedSOV) + } + } + utils.Sort(expectedActive) + + activeIterator, err := chain.GetActiveSubnetOnlyValidatorsIterator() + require.NoError(err) + require.Equal( + expectedActive, + iterator.ToSlice(activeIterator), + ) + + require.Equal(len(expectedActive), chain.NumActiveSubnetOnlyValidators()) + + for subnetID, expectedNumSOVs := range numSOVs { + numSOVs, err := chain.NumSubnetOnlyValidators(subnetID) + require.NoError(err) + require.Equal(expectedNumSOVs, numSOVs) + } + } + + verifyChain(d) + require.NoError(d.Apply(state)) + verifyChain(d) + verifyChain(state) + assertChainsEqual(t, state, d) + + state.SetHeight(1) + require.NoError(state.Commit()) + verifyChain(d) + verifyChain(state) + assertChainsEqual(t, state, d) + + sovsToValidatorSet := func( + sovs map[ids.ID]SubnetOnlyValidator, + subnetID ids.ID, + ) map[ids.NodeID]*validators.GetValidatorOutput { + validatorSet := make(map[ids.NodeID]*validators.GetValidatorOutput) + for _, sov := range sovs { + if sov.SubnetID != subnetID || sov.Weight == 0 { + continue + } + + nodeID := sov.NodeID + publicKey := bls.PublicKeyFromValidUncompressedBytes(sov.PublicKey) + // Inactive validators are combined into a single validator + // with the empty ID. + if sov.EndAccumulatedFee == 0 { + nodeID = ids.EmptyNodeID + publicKey = nil + } + + vdr, ok := validatorSet[nodeID] + if !ok { + vdr = &validators.GetValidatorOutput{ + NodeID: nodeID, + PublicKey: publicKey, + } + validatorSet[nodeID] = vdr + } + vdr.Weight += sov.Weight + } + return validatorSet + } + + for subnetID := range subnetIDs { + expectedValidatorSet := sovsToValidatorSet(initialSOVs, subnetID) + endValidatorSet := sovsToValidatorSet(expectedSOVs, subnetID) + + require.NoError(state.ApplyValidatorWeightDiffs(context.Background(), endValidatorSet, 1, 1, subnetID)) + require.NoError(state.ApplyValidatorPublicKeyDiffs(context.Background(), endValidatorSet, 1, 1, subnetID)) + require.Equal(expectedValidatorSet, endValidatorSet) + } + }) + } +} diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 7463e9b72fc6..3884abcdcbaf 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -218,10 +218,6 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state SubnetOnlyValida prevExists = true prevActive = priorSOV.EndAccumulatedFee != 0 case database.ErrNotFound: - if !newExists { - return nil // Removing a validator that didn't exist is a noop - } - has, err := state.HasSubnetOnlyValidator(sov.SubnetID, sov.NodeID) if err != nil { return err diff --git a/vms/platformvm/validators/manager.go b/vms/platformvm/validators/manager.go index 781d119e226b..02ff6e475ab2 100644 --- a/vms/platformvm/validators/manager.go +++ b/vms/platformvm/validators/manager.go @@ -85,6 +85,7 @@ type State interface { validators map[ids.NodeID]*validators.GetValidatorOutput, startHeight uint64, endHeight uint64, + subnetID ids.ID, ) error } @@ -271,7 +272,7 @@ func (m *manager) makePrimaryNetworkValidatorSet( validatorSet, currentHeight, lastDiffHeight, - constants.PlatformChainID, + constants.PrimaryNetworkID, ) if err != nil { return nil, 0, err @@ -282,6 +283,7 @@ func (m *manager) makePrimaryNetworkValidatorSet( validatorSet, currentHeight, lastDiffHeight, + constants.PrimaryNetworkID, ) return validatorSet, currentHeight, err } @@ -343,11 +345,13 @@ func (m *manager) makeSubnetValidatorSet( } } + // Prior to ACP-77, public keys were inherited from the primary network. err = m.state.ApplyValidatorPublicKeyDiffs( ctx, subnetValidatorSet, currentHeight, lastDiffHeight, + constants.PrimaryNetworkID, ) return subnetValidatorSet, currentHeight, err } From 8ef4fcff970f7cf4a89e507f9ef9e3b389e30716 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 16 Sep 2024 17:47:58 -0400 Subject: [PATCH 042/199] Store weights rather than counts --- vms/platformvm/state/diff.go | 8 +-- vms/platformvm/state/mock_chain.go | 30 +++++----- vms/platformvm/state/mock_diff.go | 30 +++++----- vms/platformvm/state/mock_state.go | 30 +++++----- vms/platformvm/state/state.go | 32 +++++------ vms/platformvm/state/state_test.go | 10 ++-- vms/platformvm/state/subnet_only_validator.go | 55 +++++++++++-------- 7 files changed, 100 insertions(+), 95 deletions(-) diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 1e51717c8d14..ae0568650580 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -206,9 +206,9 @@ func (d *diff) NumActiveSubnetOnlyValidators() int { return d.parentActiveSOVs + d.sovDiff.numAddedActive } -func (d *diff) NumSubnetOnlyValidators(subnetID ids.ID) (int, error) { - if numSOVs, modified := d.sovDiff.modifiedNumValidators[subnetID]; modified { - return numSOVs, nil +func (d *diff) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { + if weight, modified := d.sovDiff.modifiedTotalWeight[subnetID]; modified { + return weight, nil } parentState, ok := d.stateVersions.GetState(d.parentID) @@ -216,7 +216,7 @@ func (d *diff) NumSubnetOnlyValidators(subnetID ids.ID) (int, error) { return 0, fmt.Errorf("%w: %s", ErrMissingParentState, d.parentID) } - return parentState.NumSubnetOnlyValidators(subnetID) + return parentState.WeightOfSubnetOnlyValidators(subnetID) } func (d *diff) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { diff --git a/vms/platformvm/state/mock_chain.go b/vms/platformvm/state/mock_chain.go index 835389f12d79..5077a16ff69e 100644 --- a/vms/platformvm/state/mock_chain.go +++ b/vms/platformvm/state/mock_chain.go @@ -518,21 +518,6 @@ func (mr *MockChainMockRecorder) NumActiveSubnetOnlyValidators() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumActiveSubnetOnlyValidators", reflect.TypeOf((*MockChain)(nil).NumActiveSubnetOnlyValidators)) } -// NumSubnetOnlyValidators mocks base method. -func (m *MockChain) NumSubnetOnlyValidators(subnetID ids.ID) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NumSubnetOnlyValidators", subnetID) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NumSubnetOnlyValidators indicates an expected call of NumSubnetOnlyValidators. -func (mr *MockChainMockRecorder) NumSubnetOnlyValidators(subnetID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumSubnetOnlyValidators", reflect.TypeOf((*MockChain)(nil).NumSubnetOnlyValidators), subnetID) -} - // PutCurrentDelegator mocks base method. func (m *MockChain) PutCurrentDelegator(staker *Staker) { m.ctrl.T.Helper() @@ -696,3 +681,18 @@ func (mr *MockChainMockRecorder) SetTimestamp(tm any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTimestamp", reflect.TypeOf((*MockChain)(nil).SetTimestamp), tm) } + +// WeightOfSubnetOnlyValidators mocks base method. +func (m *MockChain) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WeightOfSubnetOnlyValidators", subnetID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WeightOfSubnetOnlyValidators indicates an expected call of WeightOfSubnetOnlyValidators. +func (mr *MockChainMockRecorder) WeightOfSubnetOnlyValidators(subnetID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WeightOfSubnetOnlyValidators", reflect.TypeOf((*MockChain)(nil).WeightOfSubnetOnlyValidators), subnetID) +} diff --git a/vms/platformvm/state/mock_diff.go b/vms/platformvm/state/mock_diff.go index 8c9e48d7845d..1112451386f4 100644 --- a/vms/platformvm/state/mock_diff.go +++ b/vms/platformvm/state/mock_diff.go @@ -532,21 +532,6 @@ func (mr *MockDiffMockRecorder) NumActiveSubnetOnlyValidators() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumActiveSubnetOnlyValidators", reflect.TypeOf((*MockDiff)(nil).NumActiveSubnetOnlyValidators)) } -// NumSubnetOnlyValidators mocks base method. -func (m *MockDiff) NumSubnetOnlyValidators(subnetID ids.ID) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NumSubnetOnlyValidators", subnetID) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NumSubnetOnlyValidators indicates an expected call of NumSubnetOnlyValidators. -func (mr *MockDiffMockRecorder) NumSubnetOnlyValidators(subnetID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumSubnetOnlyValidators", reflect.TypeOf((*MockDiff)(nil).NumSubnetOnlyValidators), subnetID) -} - // PutCurrentDelegator mocks base method. func (m *MockDiff) PutCurrentDelegator(staker *Staker) { m.ctrl.T.Helper() @@ -710,3 +695,18 @@ func (mr *MockDiffMockRecorder) SetTimestamp(tm any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTimestamp", reflect.TypeOf((*MockDiff)(nil).SetTimestamp), tm) } + +// WeightOfSubnetOnlyValidators mocks base method. +func (m *MockDiff) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WeightOfSubnetOnlyValidators", subnetID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WeightOfSubnetOnlyValidators indicates an expected call of WeightOfSubnetOnlyValidators. +func (mr *MockDiffMockRecorder) WeightOfSubnetOnlyValidators(subnetID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WeightOfSubnetOnlyValidators", reflect.TypeOf((*MockDiff)(nil).WeightOfSubnetOnlyValidators), subnetID) +} diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index 0f67d6607ba3..1abe784ef272 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -753,21 +753,6 @@ func (mr *MockStateMockRecorder) NumActiveSubnetOnlyValidators() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumActiveSubnetOnlyValidators", reflect.TypeOf((*MockState)(nil).NumActiveSubnetOnlyValidators)) } -// NumSubnetOnlyValidators mocks base method. -func (m *MockState) NumSubnetOnlyValidators(subnetID ids.ID) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NumSubnetOnlyValidators", subnetID) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NumSubnetOnlyValidators indicates an expected call of NumSubnetOnlyValidators. -func (mr *MockStateMockRecorder) NumSubnetOnlyValidators(subnetID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NumSubnetOnlyValidators", reflect.TypeOf((*MockState)(nil).NumSubnetOnlyValidators), subnetID) -} - // PutCurrentDelegator mocks base method. func (m *MockState) PutCurrentDelegator(staker *Staker) { m.ctrl.T.Helper() @@ -998,3 +983,18 @@ func (mr *MockStateMockRecorder) UTXOIDs(addr, previous, limit any) *gomock.Call mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UTXOIDs", reflect.TypeOf((*MockState)(nil).UTXOIDs), addr, previous, limit) } + +// WeightOfSubnetOnlyValidators mocks base method. +func (m *MockState) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WeightOfSubnetOnlyValidators", subnetID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WeightOfSubnetOnlyValidators indicates an expected call of WeightOfSubnetOnlyValidators. +func (mr *MockStateMockRecorder) WeightOfSubnetOnlyValidators(subnetID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WeightOfSubnetOnlyValidators", reflect.TypeOf((*MockState)(nil).WeightOfSubnetOnlyValidators), subnetID) +} diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index ba4fcb46eb05..ac572364e75a 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -86,7 +86,7 @@ var ( ChainPrefix = []byte("chain") ExpiryReplayProtectionPrefix = []byte("expiryReplayProtection") SubnetOnlyValidatorsPrefix = []byte("subnetOnlyValidators") - NumValidatorsPrefix = []byte("numValidators") + WeightsPrefix = []byte("weights") SubnetIDNodeIDPrefix = []byte("subnetIDNodeID") ActivePrefix = []byte("active") InactivePrefix = []byte("inactive") @@ -320,7 +320,7 @@ type state struct { activeSOVs *btree.BTreeG[SubnetOnlyValidator] sovDiff *subnetOnlyValidatorsDiff subnetOnlyValidatorsDB database.Database - numValidatorsDB database.Database + weightsDB database.Database subnetIDNodeIDDB database.Database activeDB database.Database inactiveDB database.Database @@ -646,7 +646,7 @@ func New( activeSOVs: btree.NewG(defaultTreeDegree, SubnetOnlyValidator.Less), sovDiff: newSubnetOnlyValidatorsDiff(), subnetOnlyValidatorsDB: subnetOnlyValidatorsDB, - numValidatorsDB: prefixdb.New(NumValidatorsPrefix, subnetOnlyValidatorsDB), + weightsDB: prefixdb.New(WeightsPrefix, subnetOnlyValidatorsDB), subnetIDNodeIDDB: prefixdb.New(SubnetIDNodeIDPrefix, subnetOnlyValidatorsDB), activeDB: prefixdb.New(ActivePrefix, subnetOnlyValidatorsDB), inactiveDB: prefixdb.New(InactivePrefix, subnetOnlyValidatorsDB), @@ -756,23 +756,17 @@ func (s *state) NumActiveSubnetOnlyValidators() int { return len(s.activeSOVLookup) + s.sovDiff.numAddedActive } -func (s *state) NumSubnetOnlyValidators(subnetID ids.ID) (int, error) { - if numSOVs, modified := s.sovDiff.modifiedNumValidators[subnetID]; modified { - return numSOVs, nil +func (s *state) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { + if weight, modified := s.sovDiff.modifiedTotalWeight[subnetID]; modified { + return weight, nil } // TODO: Add caching - numSOVs, err := database.GetUInt64(s.numValidatorsDB, subnetID[:]) + weight, err := database.GetUInt64(s.weightsDB, subnetID[:]) if err == database.ErrNotFound { return 0, nil } - if err != nil { - return 0, err - } - if numSOVs > math.MaxInt { - return 0, safemath.ErrOverflow - } - return int(numSOVs), nil + return weight, err } func (s *state) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { @@ -1901,7 +1895,7 @@ func (s *state) write(updateValidators bool, height uint64) error { func (s *state) Close() error { return errors.Join( s.expiryDB.Close(), - s.numValidatorsDB.Close(), + s.weightsDB.Close(), s.subnetIDNodeIDDB.Close(), s.activeDB.Close(), s.inactiveDB.Close(), @@ -2137,13 +2131,13 @@ func (s *state) writeExpiry() error { // TODO: Update validator sets // TODO: Add caching func (s *state) writeSubnetOnlyValidators(height uint64) error { - // Write counts: - for subnetID, numValidators := range s.sovDiff.modifiedNumValidators { - if err := database.PutUInt64(s.numValidatorsDB, subnetID[:], uint64(numValidators)); err != nil { + // Write modified weights: + for subnetID, weight := range s.sovDiff.modifiedTotalWeight { + if err := database.PutUInt64(s.weightsDB, subnetID[:], weight); err != nil { return err } } - maps.Clear(s.sovDiff.modifiedNumValidators) + maps.Clear(s.sovDiff.modifiedTotalWeight) historicalDiffs, err := s.makeSubnetOnlyValidatorHistoricalDiffs() if err != nil { diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 0beff373a0bf..62d155283bdc 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1922,7 +1922,7 @@ func TestSubnetOnlyValidators(t *testing.T) { } var ( - numSOVs = make(map[ids.ID]int) + weights = make(map[ids.ID]uint64) expectedActive []SubnetOnlyValidator ) for _, expectedSOV := range expectedSOVs { @@ -1938,7 +1938,7 @@ func TestSubnetOnlyValidators(t *testing.T) { require.NoError(err) require.True(has) - numSOVs[sov.SubnetID]++ + weights[sov.SubnetID] += sov.Weight if expectedSOV.isActive() { expectedActive = append(expectedActive, expectedSOV) } @@ -1954,10 +1954,10 @@ func TestSubnetOnlyValidators(t *testing.T) { require.Equal(len(expectedActive), chain.NumActiveSubnetOnlyValidators()) - for subnetID, expectedNumSOVs := range numSOVs { - numSOVs, err := chain.NumSubnetOnlyValidators(subnetID) + for subnetID, expectedWeight := range weights { + weight, err := chain.WeightOfSubnetOnlyValidators(subnetID) require.NoError(err) - require.Equal(expectedNumSOVs, numSOVs) + require.Equal(expectedWeight, weight) } } diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 3884abcdcbaf..9b505cb22842 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -14,6 +14,8 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/vms/platformvm/block" + + safemath "github.com/ava-labs/avalanchego/utils/math" ) var ( @@ -32,9 +34,9 @@ type SubnetOnlyValidators interface { // subnet only validators. NumActiveSubnetOnlyValidators() int - // NumSubnetOnlyValidators returns the total number of subnet only - // validators on [subnetID]. - NumSubnetOnlyValidators(subnetID ids.ID) (int, error) + // WeightOfSubnetOnlyValidators returns the total active and inactive weight + // of subnet only validators on [subnetID]. + WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) // GetSubnetOnlyValidator returns the validator with [validationID] if it // exists. If the validator does not exist, [err] will equal @@ -166,19 +168,19 @@ type subnetIDNodeID struct { } type subnetOnlyValidatorsDiff struct { - numAddedActive int // May be negative - modifiedNumValidators map[ids.ID]int // subnetID -> numValidators - modified map[ids.ID]SubnetOnlyValidator - modifiedHasNodeIDs map[subnetIDNodeID]bool - active *btree.BTreeG[SubnetOnlyValidator] + numAddedActive int // May be negative + modifiedTotalWeight map[ids.ID]uint64 // subnetID -> totalWeight + modified map[ids.ID]SubnetOnlyValidator + modifiedHasNodeIDs map[subnetIDNodeID]bool + active *btree.BTreeG[SubnetOnlyValidator] } func newSubnetOnlyValidatorsDiff() *subnetOnlyValidatorsDiff { return &subnetOnlyValidatorsDiff{ - modifiedNumValidators: make(map[ids.ID]int), - modified: make(map[ids.ID]SubnetOnlyValidator), - modifiedHasNodeIDs: make(map[subnetIDNodeID]bool), - active: btree.NewG(defaultTreeDegree, SubnetOnlyValidator.Less), + modifiedTotalWeight: make(map[ids.ID]uint64), + modified: make(map[ids.ID]SubnetOnlyValidator), + modifiedHasNodeIDs: make(map[subnetIDNodeID]bool), + active: btree.NewG(defaultTreeDegree, SubnetOnlyValidator.Less), } } @@ -204,10 +206,9 @@ func (d *subnetOnlyValidatorsDiff) hasSubnetOnlyValidator(subnetID ids.ID, nodeI func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state SubnetOnlyValidators, sov SubnetOnlyValidator) error { var ( - prevExists bool + prevWeight uint64 prevActive bool - newExists = sov.Weight != 0 - newActive = newExists && sov.EndAccumulatedFee != 0 + newActive = sov.Weight != 0 && sov.EndAccumulatedFee != 0 ) switch priorSOV, err := state.GetSubnetOnlyValidator(sov.ValidationID); err { case nil: @@ -215,7 +216,7 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state SubnetOnlyValida return ErrMutatedSubnetOnlyValidator } - prevExists = true + prevWeight = priorSOV.Weight prevActive = priorSOV.EndAccumulatedFee != 0 case database.ErrNotFound: has, err := state.HasSubnetOnlyValidator(sov.SubnetID, sov.NodeID) @@ -230,20 +231,30 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state SubnetOnlyValida } switch { - case prevExists && !newExists: - numSOVs, err := state.NumSubnetOnlyValidators(sov.SubnetID) + case prevWeight < sov.Weight: + weight, err := state.WeightOfSubnetOnlyValidators(sov.SubnetID) + if err != nil { + return err + } + + weight, err = safemath.Add(weight, sov.Weight-prevWeight) + if err != nil { + return err + } + + d.modifiedTotalWeight[sov.SubnetID] = weight + case prevWeight > sov.Weight: + weight, err := state.WeightOfSubnetOnlyValidators(sov.SubnetID) if err != nil { return err } - d.modifiedNumValidators[sov.SubnetID] = numSOVs - 1 - case !prevExists && newExists: - numSOVs, err := state.NumSubnetOnlyValidators(sov.SubnetID) + weight, err = safemath.Sub(weight, prevWeight-sov.Weight) if err != nil { return err } - d.modifiedNumValidators[sov.SubnetID] = numSOVs + 1 + d.modifiedTotalWeight[sov.SubnetID] = weight } switch { From e2045e23b136a9895d76fb40e7e7d7bba952ae46 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 16 Sep 2024 17:58:10 -0400 Subject: [PATCH 043/199] load sov validators on startup --- vms/platformvm/state/state.go | 48 ++++++++++++++++++++++++++++-- vms/platformvm/state/state_test.go | 13 +++++--- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index ac572364e75a..b34c9ea16f2e 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1830,9 +1830,53 @@ func (s *state) loadPendingValidators() error { ) } -// Invariant: initValidatorSets requires loadCurrentValidators to have already -// been called. +// Invariant: initValidatorSets requires loadActiveSubnetOnlyValidators and +// loadCurrentValidators to have already been called. func (s *state) initValidatorSets() error { + // Load ACP77 validators + for validationID, sov := range s.activeSOVLookup { + pk := bls.PublicKeyFromValidUncompressedBytes(sov.PublicKey) + if err := s.validators.AddStaker(sov.SubnetID, sov.NodeID, pk, validationID, sov.Weight); err != nil { + return err + } + } + + // Load inactive weights + it := s.weightsDB.NewIterator() + defer it.Release() + + for it.Next() { + subnetID, err := ids.ToID(it.Key()) + if err != nil { + return err + } + + totalWeight, err := database.ParseUInt64(it.Value()) + if err != nil { + return err + } + + activeWeight, err := s.validators.TotalWeight(subnetID) + if err != nil { + return err + } + + inactiveWeight, err := safemath.Sub(totalWeight, activeWeight) + if err != nil { + // This should never happen, as the total weight should always be at + // least the sum of the active weights. + return err + } + if inactiveWeight == 0 { + continue + } + + if err := s.validators.AddStaker(subnetID, ids.EmptyNodeID, nil, ids.Empty, inactiveWeight); err != nil { + return err + } + } + + // Load primary network and non-ACP77 validators for subnetID, validators := range s.currentStakers.validators { if s.validators.Count(subnetID) != 0 { // Enforce the invariant that the validator set is empty here. diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 62d155283bdc..318e52b196b5 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1885,7 +1885,8 @@ func TestSubnetOnlyValidators(t *testing.T) { t.Run(test.name, func(t *testing.T) { require := require.New(t) - state := newTestState(t, memdb.New()) + db := memdb.New() + state := newTestState(t, db) var ( initialSOVs = make(map[ids.ID]SubnetOnlyValidator) @@ -2005,13 +2006,17 @@ func TestSubnetOnlyValidators(t *testing.T) { return validatorSet } + reloadedState := newTestState(t, db) for subnetID := range subnetIDs { - expectedValidatorSet := sovsToValidatorSet(initialSOVs, subnetID) - endValidatorSet := sovsToValidatorSet(expectedSOVs, subnetID) + expectedEndValidatorSet := sovsToValidatorSet(expectedSOVs, subnetID) + endValidatorSet := reloadedState.validators.GetMap(subnetID) + require.Equal(expectedEndValidatorSet, endValidatorSet) require.NoError(state.ApplyValidatorWeightDiffs(context.Background(), endValidatorSet, 1, 1, subnetID)) require.NoError(state.ApplyValidatorPublicKeyDiffs(context.Background(), endValidatorSet, 1, 1, subnetID)) - require.Equal(expectedValidatorSet, endValidatorSet) + + initialValidatorSet := sovsToValidatorSet(initialSOVs, subnetID) + require.Equal(initialValidatorSet, endValidatorSet) } }) } From e0beab1f26b19d5af7bea74cb08915a1cc032f0b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 16 Sep 2024 22:59:40 -0400 Subject: [PATCH 044/199] update in-memory validator sets --- vms/platformvm/state/state.go | 132 ++++++++++++++++++++++++++--- vms/platformvm/state/state_test.go | 5 +- 2 files changed, 123 insertions(+), 14 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index b34c9ea16f2e..5ac7419102bf 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1919,7 +1919,7 @@ func (s *state) write(updateValidators bool, height uint64) error { return errors.Join( s.writeBlocks(), s.writeExpiry(), - s.writeSubnetOnlyValidators(height), + s.writeSubnetOnlyValidators(updateValidators, height), s.writeCurrentStakers(updateValidators, height, codecVersion), s.writePendingStakers(), s.WriteValidatorMetadata(s.currentValidatorList, s.currentSubnetValidatorList, codecVersion), // Must be called after writeCurrentStakers @@ -2174,7 +2174,7 @@ func (s *state) writeExpiry() error { // TODO: Update validator sets // TODO: Add caching -func (s *state) writeSubnetOnlyValidators(height uint64) error { +func (s *state) writeSubnetOnlyValidators(updateValidators bool, height uint64) error { // Write modified weights: for subnetID, weight := range s.sovDiff.modifiedTotalWeight { if err := database.PutUInt64(s.weightsDB, subnetID[:], weight); err != nil { @@ -2209,12 +2209,17 @@ func (s *state) writeSubnetOnlyValidators(height uint64) error { } } + sovChanges := s.sovDiff.modified // Perform deletions: - for validationID, sov := range s.sovDiff.modified { + for validationID, sov := range sovChanges { if sov.Weight != 0 { + // Additions and modifications are handled in the next loops. continue } + // The next loops shouldn't consider this change. + delete(sovChanges, validationID) + priorSOV, err := s.getPersistedSubnetOnlyValidator(validationID) if err == database.ErrNotFound { // Deleting a non-existent validator is a noop. This can happen if @@ -2242,22 +2247,33 @@ func (s *state) writeSubnetOnlyValidators(height uint64) error { if err != nil { return err } - } - // Perform additions/modifications: - for validationID, sov := range s.sovDiff.modified { - if sov.Weight == 0 { + + // TODO: Move the validator set management out of the state package + if !updateValidators { continue } - subnetIDNodeIDKey := make([]byte, len(sov.SubnetID)+len(sov.NodeID)) - copy(subnetIDNodeIDKey, sov.SubnetID[:]) - copy(subnetIDNodeIDKey[len(sov.SubnetID):], sov.NodeID[:]) - if err := s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, nil); err != nil { + nodeID := ids.EmptyNodeID + if priorSOV.isActive() { + nodeID = priorSOV.NodeID + } + if err := s.validators.RemoveWeight(priorSOV.SubnetID, nodeID, priorSOV.Weight); err != nil { + return fmt.Errorf("failed to delete SoV validator: %w", err) + } + } + + // Perform modifications: + for validationID, sov := range sovChanges { + priorSOV, err := s.getPersistedSubnetOnlyValidator(validationID) + if err == database.ErrNotFound { + // New additions are handled in the next loop. + continue + } + if err != nil { return err } - var err error - if priorSOV, ok := s.activeSOVLookup[validationID]; ok { + if priorSOV.isActive() { delete(s.activeSOVLookup, validationID) s.activeSOVs.Delete(priorSOV) err = deleteSubnetOnlyValidator(s.activeDB, validationID) @@ -2278,6 +2294,96 @@ func (s *state) writeSubnetOnlyValidators(height uint64) error { if err != nil { return err } + + // The next loop shouldn't consider this change. + delete(sovChanges, validationID) + + // TODO: Move the validator set management out of the state package + if !updateValidators { + continue + } + + switch { + case !priorSOV.isActive() && sov.isActive(): + // This validator is being activated. + pk := bls.PublicKeyFromValidUncompressedBytes(sov.PublicKey) + err = errors.Join( + s.validators.RemoveWeight(sov.SubnetID, ids.EmptyNodeID, priorSOV.Weight), + s.validators.AddStaker(sov.SubnetID, sov.NodeID, pk, validationID, sov.Weight), + ) + case priorSOV.isActive() && !sov.isActive(): + // This validator is being deactivated. + inactiveWeight := s.validators.GetWeight(sov.SubnetID, ids.EmptyNodeID) + if inactiveWeight == 0 { + err = s.validators.AddStaker(sov.SubnetID, ids.EmptyNodeID, nil, ids.Empty, sov.Weight) + } else { + err = s.validators.AddWeight(sov.SubnetID, ids.EmptyNodeID, sov.Weight) + } + err = errors.Join( + err, + s.validators.RemoveWeight(sov.SubnetID, sov.NodeID, priorSOV.Weight), + ) + default: + // This validator's active status isn't changing. + nodeID := ids.EmptyNodeID + if sov.isActive() { + nodeID = sov.NodeID + } + if priorSOV.Weight < sov.Weight { + err = s.validators.AddWeight(sov.SubnetID, nodeID, sov.Weight-priorSOV.Weight) + } else if priorSOV.Weight > sov.Weight { + err = s.validators.RemoveWeight(sov.SubnetID, nodeID, priorSOV.Weight-sov.Weight) + } + } + if err != nil { + return err + } + } + + // Perform additions: + for validationID, sov := range sovChanges { + subnetIDNodeIDKey := make([]byte, len(sov.SubnetID)+len(sov.NodeID)) + copy(subnetIDNodeIDKey, sov.SubnetID[:]) + copy(subnetIDNodeIDKey[len(sov.SubnetID):], sov.NodeID[:]) + if err := s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, nil); err != nil { + return err + } + + isActive := sov.isActive() + if isActive { + s.activeSOVLookup[validationID] = sov + s.activeSOVs.ReplaceOrInsert(sov) + err = putSubnetOnlyValidator(s.activeDB, sov) + } else { + err = putSubnetOnlyValidator(s.inactiveDB, sov) + } + if err != nil { + return err + } + + // TODO: Move the validator set management out of the state package + if !updateValidators { + continue + } + + if isActive { + pk := bls.PublicKeyFromValidUncompressedBytes(sov.PublicKey) + if err := s.validators.AddStaker(sov.SubnetID, sov.NodeID, pk, validationID, sov.Weight); err != nil { + return fmt.Errorf("failed to add SoV validator: %w", err) + } + continue + } + + // This validator is inactive + inactiveWeight := s.validators.GetWeight(sov.SubnetID, ids.EmptyNodeID) + if inactiveWeight == 0 { + err = s.validators.AddStaker(sov.SubnetID, ids.EmptyNodeID, nil, ids.Empty, sov.Weight) + } else { + err = s.validators.AddWeight(sov.SubnetID, ids.EmptyNodeID, sov.Weight) + } + if err != nil { + return err + } } s.sovDiff = newSubnetOnlyValidatorsDiff() diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 318e52b196b5..32b515e5c713 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -2009,9 +2009,12 @@ func TestSubnetOnlyValidators(t *testing.T) { reloadedState := newTestState(t, db) for subnetID := range subnetIDs { expectedEndValidatorSet := sovsToValidatorSet(expectedSOVs, subnetID) - endValidatorSet := reloadedState.validators.GetMap(subnetID) + endValidatorSet := state.validators.GetMap(subnetID) require.Equal(expectedEndValidatorSet, endValidatorSet) + reloadedEndValidatorSet := reloadedState.validators.GetMap(subnetID) + require.Equal(expectedEndValidatorSet, reloadedEndValidatorSet) + require.NoError(state.ApplyValidatorWeightDiffs(context.Background(), endValidatorSet, 1, 1, subnetID)) require.NoError(state.ApplyValidatorPublicKeyDiffs(context.Background(), endValidatorSet, 1, 1, subnetID)) From 410bcda8035b55431028d3eae42db65d42af3303 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 17 Sep 2024 09:57:34 -0400 Subject: [PATCH 045/199] remove comment --- vms/platformvm/state/state.go | 1 - 1 file changed, 1 deletion(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 5ac7419102bf..bac3f27414ab 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2172,7 +2172,6 @@ func (s *state) writeExpiry() error { return nil } -// TODO: Update validator sets // TODO: Add caching func (s *state) writeSubnetOnlyValidators(updateValidators bool, height uint64) error { // Write modified weights: From 149e21e47169618702371e6943fec5df296693a2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 17 Sep 2024 13:45:30 -0400 Subject: [PATCH 046/199] Add SoV excess --- database/helpers.go | 8 ++++ .../block/executor/proposal_block_test.go | 2 + .../block/executor/standard_block_test.go | 2 + .../block/executor/verifier_test.go | 5 +++ vms/platformvm/state/diff.go | 11 ++++++ vms/platformvm/state/diff_test.go | 24 ++++++++++++ vms/platformvm/state/mock_chain.go | 26 +++++++++++++ vms/platformvm/state/mock_diff.go | 26 +++++++++++++ vms/platformvm/state/mock_state.go | 26 +++++++++++++ vms/platformvm/state/state.go | 37 ++++++++++++++----- 10 files changed, 158 insertions(+), 9 deletions(-) diff --git a/database/helpers.go b/database/helpers.go index d17e6669e4fa..43fff80796ad 100644 --- a/database/helpers.go +++ b/database/helpers.go @@ -54,6 +54,14 @@ func GetUInt64(db KeyValueReader, key []byte) (uint64, error) { return ParseUInt64(b) } +func GetOrDefaultUInt64(db KeyValueReader, key []byte, def uint64) (uint64, error) { + v, err := GetUInt64(db, key) + if err == ErrNotFound { + return def, nil + } + return v, err +} + func PackUInt64(val uint64) []byte { bytes := make([]byte, Uint64Size) binary.BigEndian.PutUint64(bytes, val) diff --git a/vms/platformvm/block/executor/proposal_block_test.go b/vms/platformvm/block/executor/proposal_block_test.go index 0708704ebabf..2e0aaa72bc03 100644 --- a/vms/platformvm/block/executor/proposal_block_test.go +++ b/vms/platformvm/block/executor/proposal_block_test.go @@ -91,6 +91,7 @@ func TestApricotProposalBlockTimeVerification(t *testing.T) { onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() + onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() onParentAccept.EXPECT().NumActiveSubnetOnlyValidators().Return(0).AnyTimes() currentStakersIt := iteratormock.NewIterator[*state.Staker](ctrl) @@ -164,6 +165,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { onParentAccept.EXPECT().GetTimestamp().Return(parentTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() + onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() onParentAccept.EXPECT().NumActiveSubnetOnlyValidators().Return(0).AnyTimes() onParentAccept.EXPECT().GetCurrentSupply(constants.PrimaryNetworkID).Return(uint64(1000), nil).AnyTimes() diff --git a/vms/platformvm/block/executor/standard_block_test.go b/vms/platformvm/block/executor/standard_block_test.go index 1bdd822144f3..3162bb8998ae 100644 --- a/vms/platformvm/block/executor/standard_block_test.go +++ b/vms/platformvm/block/executor/standard_block_test.go @@ -60,6 +60,7 @@ func TestApricotStandardBlockTimeVerification(t *testing.T) { onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() + onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() onParentAccept.EXPECT().NumActiveSubnetOnlyValidators().Return(0).AnyTimes() // wrong height @@ -139,6 +140,7 @@ func TestBanffStandardBlockTimeVerification(t *testing.T) { onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() + onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() onParentAccept.EXPECT().NumActiveSubnetOnlyValidators().Return(0).AnyTimes() txID := ids.GenerateTestID() diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index bf44ff70d7a6..9b678e95cde9 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -104,6 +104,7 @@ func TestVerifierVisitProposalBlock(t *testing.T) { parentOnAcceptState.EXPECT().GetTimestamp().Return(timestamp).Times(2) parentOnAcceptState.EXPECT().GetFeeState().Return(gas.State{}).Times(2) parentOnAcceptState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(2) + parentOnAcceptState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(2) parentOnAcceptState.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(2) backend := &backend{ @@ -337,6 +338,7 @@ func TestVerifierVisitStandardBlock(t *testing.T) { parentState.EXPECT().GetTimestamp().Return(timestamp).Times(1) parentState.EXPECT().GetFeeState().Return(gas.State{}).Times(1) parentState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + parentState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) parentState.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) mempool.EXPECT().Remove(apricotBlk.Txs()).Times(1) @@ -600,6 +602,7 @@ func TestBanffAbortBlockTimestampChecks(t *testing.T) { s.EXPECT().GetTimestamp().Return(parentTime).Times(3) s.EXPECT().GetFeeState().Return(gas.State{}).Times(3) s.EXPECT().GetAccruedFees().Return(uint64(0)).Times(3) + s.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(3) s.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(3) onDecisionState, err := state.NewDiff(parentID, backend) @@ -699,6 +702,7 @@ func TestBanffCommitBlockTimestampChecks(t *testing.T) { s.EXPECT().GetTimestamp().Return(parentTime).Times(3) s.EXPECT().GetFeeState().Return(gas.State{}).Times(3) s.EXPECT().GetAccruedFees().Return(uint64(0)).Times(3) + s.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(3) s.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(3) onDecisionState, err := state.NewDiff(parentID, backend) @@ -816,6 +820,7 @@ func TestVerifierVisitStandardBlockWithDuplicateInputs(t *testing.T) { parentState.EXPECT().GetTimestamp().Return(timestamp).Times(1) parentState.EXPECT().GetFeeState().Return(gas.State{}).Times(1) parentState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + parentState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) parentState.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) parentStatelessBlk.EXPECT().Parent().Return(grandParentID).Times(1) diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index ae0568650580..5110aa1ea6e0 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -37,6 +37,7 @@ type diff struct { timestamp time.Time feeState gas.State + sovExcess gas.Gas accruedFees uint64 parentActiveSOVs int @@ -82,6 +83,7 @@ func NewDiff( stateVersions: stateVersions, timestamp: parentState.GetTimestamp(), feeState: parentState.GetFeeState(), + sovExcess: parentState.GetSoVExcess(), accruedFees: parentState.GetAccruedFees(), parentActiveSOVs: parentState.NumActiveSubnetOnlyValidators(), expiryDiff: newExpiryDiff(), @@ -121,6 +123,14 @@ func (d *diff) SetFeeState(feeState gas.State) { d.feeState = feeState } +func (d *diff) GetSoVExcess() gas.Gas { + return d.sovExcess +} + +func (d *diff) SetSoVExcess(excess gas.Gas) { + d.sovExcess = excess +} + func (d *diff) GetAccruedFees() uint64 { return d.accruedFees } @@ -553,6 +563,7 @@ func (d *diff) DeleteUTXO(utxoID ids.ID) { func (d *diff) Apply(baseState Chain) error { baseState.SetTimestamp(d.timestamp) baseState.SetFeeState(d.feeState) + baseState.SetSoVExcess(d.sovExcess) baseState.SetAccruedFees(d.accruedFees) for subnetID, supply := range d.currentSupply { baseState.SetCurrentSupply(subnetID, supply) diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index 187667c34e03..86767348047e 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -70,6 +70,24 @@ func TestDiffFeeState(t *testing.T) { assertChainsEqual(t, state, d) } +func TestDiffSoVExcess(t *testing.T) { + require := require.New(t) + + state := newTestState(t, memdb.New()) + + d, err := NewDiffOn(state) + require.NoError(err) + + initialExcess := state.GetSoVExcess() + newExcess := initialExcess + 1 + d.SetSoVExcess(newExcess) + require.Equal(newExcess, d.GetSoVExcess()) + require.Equal(initialExcess, state.GetSoVExcess()) + + require.NoError(d.Apply(state)) + assertChainsEqual(t, state, d) +} + func TestDiffAccruedFees(t *testing.T) { require := require.New(t) @@ -353,6 +371,7 @@ func TestDiffCurrentValidator(t *testing.T) { state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) d, err := NewDiffOn(state) @@ -389,6 +408,7 @@ func TestDiffPendingValidator(t *testing.T) { state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) d, err := NewDiffOn(state) @@ -431,6 +451,7 @@ func TestDiffCurrentDelegator(t *testing.T) { state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) d, err := NewDiffOn(state) @@ -479,6 +500,7 @@ func TestDiffPendingDelegator(t *testing.T) { state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) d, err := NewDiffOn(state) @@ -621,6 +643,7 @@ func TestDiffTx(t *testing.T) { state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) d, err := NewDiffOn(state) @@ -720,6 +743,7 @@ func TestDiffUTXO(t *testing.T) { state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) d, err := NewDiffOn(state) diff --git a/vms/platformvm/state/mock_chain.go b/vms/platformvm/state/mock_chain.go index 5077a16ff69e..4d34407ee3c2 100644 --- a/vms/platformvm/state/mock_chain.go +++ b/vms/platformvm/state/mock_chain.go @@ -368,6 +368,20 @@ func (mr *MockChainMockRecorder) GetPendingValidator(subnetID, nodeID any) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingValidator", reflect.TypeOf((*MockChain)(nil).GetPendingValidator), subnetID, nodeID) } +// GetSoVExcess mocks base method. +func (m *MockChain) GetSoVExcess() gas.Gas { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSoVExcess") + ret0, _ := ret[0].(gas.Gas) + return ret0 +} + +// GetSoVExcess indicates an expected call of GetSoVExcess. +func (mr *MockChainMockRecorder) GetSoVExcess() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSoVExcess", reflect.TypeOf((*MockChain)(nil).GetSoVExcess)) +} + // GetSubnetManager mocks base method. func (m *MockChain) GetSubnetManager(subnetID ids.ID) (ids.ID, []byte, error) { m.ctrl.T.Helper() @@ -646,6 +660,18 @@ func (mr *MockChainMockRecorder) SetFeeState(f any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFeeState", reflect.TypeOf((*MockChain)(nil).SetFeeState), f) } +// SetSoVExcess mocks base method. +func (m *MockChain) SetSoVExcess(e gas.Gas) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetSoVExcess", e) +} + +// SetSoVExcess indicates an expected call of SetSoVExcess. +func (mr *MockChainMockRecorder) SetSoVExcess(e any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSoVExcess", reflect.TypeOf((*MockChain)(nil).SetSoVExcess), e) +} + // SetSubnetManager mocks base method. func (m *MockChain) SetSubnetManager(subnetID, chainID ids.ID, addr []byte) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/mock_diff.go b/vms/platformvm/state/mock_diff.go index 1112451386f4..95be0ff1fb5e 100644 --- a/vms/platformvm/state/mock_diff.go +++ b/vms/platformvm/state/mock_diff.go @@ -382,6 +382,20 @@ func (mr *MockDiffMockRecorder) GetPendingValidator(subnetID, nodeID any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingValidator", reflect.TypeOf((*MockDiff)(nil).GetPendingValidator), subnetID, nodeID) } +// GetSoVExcess mocks base method. +func (m *MockDiff) GetSoVExcess() gas.Gas { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSoVExcess") + ret0, _ := ret[0].(gas.Gas) + return ret0 +} + +// GetSoVExcess indicates an expected call of GetSoVExcess. +func (mr *MockDiffMockRecorder) GetSoVExcess() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSoVExcess", reflect.TypeOf((*MockDiff)(nil).GetSoVExcess)) +} + // GetSubnetManager mocks base method. func (m *MockDiff) GetSubnetManager(subnetID ids.ID) (ids.ID, []byte, error) { m.ctrl.T.Helper() @@ -660,6 +674,18 @@ func (mr *MockDiffMockRecorder) SetFeeState(f any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFeeState", reflect.TypeOf((*MockDiff)(nil).SetFeeState), f) } +// SetSoVExcess mocks base method. +func (m *MockDiff) SetSoVExcess(e gas.Gas) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetSoVExcess", e) +} + +// SetSoVExcess indicates an expected call of SetSoVExcess. +func (mr *MockDiffMockRecorder) SetSoVExcess(e any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSoVExcess", reflect.TypeOf((*MockDiff)(nil).SetSoVExcess), e) +} + // SetSubnetManager mocks base method. func (m *MockDiff) SetSubnetManager(subnetID, chainID ids.ID, addr []byte) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index 1abe784ef272..aa72ae2cbca8 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -542,6 +542,20 @@ func (mr *MockStateMockRecorder) GetRewardUTXOs(txID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRewardUTXOs", reflect.TypeOf((*MockState)(nil).GetRewardUTXOs), txID) } +// GetSoVExcess mocks base method. +func (m *MockState) GetSoVExcess() gas.Gas { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSoVExcess") + ret0, _ := ret[0].(gas.Gas) + return ret0 +} + +// GetSoVExcess indicates an expected call of GetSoVExcess. +func (mr *MockStateMockRecorder) GetSoVExcess() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSoVExcess", reflect.TypeOf((*MockState)(nil).GetSoVExcess)) +} + // GetStartTime mocks base method. func (m *MockState) GetStartTime(nodeID ids.NodeID, subnetID ids.ID) (time.Time, error) { m.ctrl.T.Helper() @@ -919,6 +933,18 @@ func (mr *MockStateMockRecorder) SetLastAccepted(blkID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLastAccepted", reflect.TypeOf((*MockState)(nil).SetLastAccepted), blkID) } +// SetSoVExcess mocks base method. +func (m *MockState) SetSoVExcess(e gas.Gas) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetSoVExcess", e) +} + +// SetSoVExcess indicates an expected call of SetSoVExcess. +func (mr *MockStateMockRecorder) SetSoVExcess(e any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSoVExcess", reflect.TypeOf((*MockState)(nil).SetSoVExcess), e) +} + // SetSubnetManager mocks base method. func (m *MockState) SetSubnetManager(subnetID, chainID ids.ID, addr []byte) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index bac3f27414ab..1c50cdd0b9a2 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -94,6 +94,7 @@ var ( TimestampKey = []byte("timestamp") FeeStateKey = []byte("fee state") + SoVExcessKey = []byte("sov excess") AccruedFeesKey = []byte("accrued fees") CurrentSupplyKey = []byte("current supply") LastAcceptedKey = []byte("last accepted") @@ -118,6 +119,9 @@ type Chain interface { GetFeeState() gas.State SetFeeState(f gas.State) + GetSoVExcess() gas.Gas + SetSoVExcess(e gas.Gas) + GetAccruedFees() uint64 SetAccruedFees(f uint64) @@ -296,6 +300,7 @@ type stateBlk struct { * |-- blocksReindexedKey -> nil * |-- timestampKey -> timestamp * |-- feeStateKey -> feeState + * |-- sovExcessKey -> sovExcess * |-- accruedFeesKey -> accruedFees * |-- currentSupplyKey -> currentSupply * |-- lastAcceptedKey -> lastAccepted @@ -402,6 +407,7 @@ type state struct { // The persisted fields represent the current database value timestamp, persistedTimestamp time.Time feeState, persistedFeeState gas.State + sovExcess, persistedSOVExcess gas.Gas accruedFees, persistedAccruedFees uint64 currentSupply, persistedCurrentSupply uint64 // [lastAccepted] is the most recently accepted block. @@ -1179,6 +1185,14 @@ func (s *state) SetFeeState(feeState gas.State) { s.feeState = feeState } +func (s *state) GetSoVExcess() gas.Gas { + return s.sovExcess +} + +func (s *state) SetSoVExcess(e gas.Gas) { + s.sovExcess = e +} + func (s *state) GetAccruedFees() uint64 { return s.accruedFees } @@ -1481,7 +1495,14 @@ func (s *state) loadMetadata() error { s.persistedFeeState = feeState s.SetFeeState(feeState) - accruedFees, err := getAccruedFees(s.singletonDB) + sovExcess, err := database.GetOrDefaultUInt64(s.singletonDB, SoVExcessKey, 0) + if err != nil { + return err + } + s.persistedSOVExcess = gas.Gas(sovExcess) + s.SetSoVExcess(gas.Gas(sovExcess)) + + accruedFees, err := database.GetOrDefaultUInt64(s.singletonDB, AccruedFeesKey, 0) if err != nil { return err } @@ -2904,6 +2925,12 @@ func (s *state) writeMetadata() error { } s.persistedFeeState = s.feeState } + if s.sovExcess != s.persistedSOVExcess { + if err := database.PutUInt64(s.singletonDB, SoVExcessKey, uint64(s.sovExcess)); err != nil { + return fmt.Errorf("failed to write sov excess: %w", err) + } + s.persistedSOVExcess = s.sovExcess + } if s.accruedFees != s.persistedAccruedFees { if err := database.PutUInt64(s.singletonDB, AccruedFeesKey, s.accruedFees); err != nil { return fmt.Errorf("failed to write accrued fees: %w", err) @@ -3121,11 +3148,3 @@ func getFeeState(db database.KeyValueReader) (gas.State, error) { } return feeState, nil } - -func getAccruedFees(db database.KeyValueReader) (uint64, error) { - accruedFees, err := database.GetUInt64(db, AccruedFeesKey) - if err == database.ErrNotFound { - return 0, nil - } - return accruedFees, err -} From b22efc68ecfe429d9afd41a6ecdf56ad88defa88 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 17 Sep 2024 16:39:40 -0400 Subject: [PATCH 047/199] Update SecondsUntil --- vms/platformvm/validators/fee/fee.go | 65 +++------- vms/platformvm/validators/fee/fee_test.go | 144 +++++++++++++++++++--- 2 files changed, 145 insertions(+), 64 deletions(-) diff --git a/vms/platformvm/validators/fee/fee.go b/vms/platformvm/validators/fee/fee.go index be0eae544ea2..16c9c35cfac6 100644 --- a/vms/platformvm/validators/fee/fee.go +++ b/vms/platformvm/validators/fee/fee.go @@ -91,35 +91,24 @@ func (s State) CostOf(c Config, seconds uint64) uint64 { return cost } -// SecondsUntil calculates the number of seconds that it would take to charge at -// least [targetCost] based on the dynamic fee mechanism. The result is capped -// at [maxSeconds]. -func (s State) SecondsUntil(c Config, maxSeconds uint64, targetCost uint64) uint64 { +// SecondsUntil calculates the maximum number of seconds that a validator can +// pay fees before their [costLimit] would be exceeded based on the dynamic fee +// mechanism. The result is capped at [maxSeconds]. +func (s State) SecondsUntil(c Config, maxSeconds uint64, costLimit uint64) uint64 { // Because this function can divide by prices, we need to sanity check the // parameters to avoid division by 0. if c.MinPrice == 0 { - if targetCost == 0 { - return 0 - } return maxSeconds } // If the current and target are the same, the price is constant. if s.Current == c.Target { - price := gas.CalculatePrice(c.MinPrice, s.Excess, c.ExcessConversionConstant) - return secondsUntil( - uint64(price), - maxSeconds, - targetCost, - ) + price := uint64(gas.CalculatePrice(c.MinPrice, s.Excess, c.ExcessConversionConstant)) + seconds := costLimit / price + return min(seconds, maxSeconds) } - var ( - cost uint64 - seconds uint64 - err error - ) - for cost < targetCost && seconds < maxSeconds { + for seconds := uint64(0); seconds < maxSeconds; seconds++ { s = s.AdvanceTime(c.Target, 1) // Advancing the time is going to either hold excess constant, @@ -127,41 +116,21 @@ func (s State) SecondsUntil(c Config, maxSeconds uint64, targetCost uint64) uint // equal to 0 after performing one of these operations, it is guaranteed // to always remain 0. if s.Excess == 0 { - zeroExcessCost := targetCost - cost - secondsWithZeroExcess := secondsUntil( - uint64(c.MinPrice), - maxSeconds, - zeroExcessCost, - ) - + secondsWithZeroExcess := costLimit / uint64(c.MinPrice) totalSeconds, err := safemath.Add(seconds, secondsWithZeroExcess) - if err != nil || totalSeconds >= maxSeconds { + if err != nil { + // This is technically unreachable, but makes the code more + // clearly correct. return maxSeconds } - return totalSeconds + return min(totalSeconds, maxSeconds) } - seconds++ - price := gas.CalculatePrice(c.MinPrice, s.Excess, c.ExcessConversionConstant) - cost, err = safemath.Add(cost, uint64(price)) - if err != nil { + price := uint64(gas.CalculatePrice(c.MinPrice, s.Excess, c.ExcessConversionConstant)) + if price > costLimit { return seconds } + costLimit -= price } - return seconds -} - -// Calculate the number of seconds that it would take to charge at least [cost] -// at [price] every second. The result is capped at [maxSeconds]. -func secondsUntil(price uint64, maxSeconds uint64, cost uint64) uint64 { - // Directly rounding up could cause an overflow. Instead we round down and - // then check if we should have rounded up. - secondsRoundedDown := cost / price - if secondsRoundedDown >= maxSeconds { - return maxSeconds - } - if cost%price == 0 { - return secondsRoundedDown - } - return secondsRoundedDown + 1 + return maxSeconds } diff --git a/vms/platformvm/validators/fee/fee_test.go b/vms/platformvm/validators/fee/fee_test.go index 059fbad61c51..602050d72c6b 100644 --- a/vms/platformvm/validators/fee/fee_test.go +++ b/vms/platformvm/validators/fee/fee_test.go @@ -212,7 +212,7 @@ var ( expectedExcess: math.MaxUint64, // Should not overflow }, { - name: "excess=0, current>>target, 11 seconds", + name: "excess=0, current>>target, 10 seconds", state: State{ Current: math.MaxUint32, Excess: 0, @@ -222,9 +222,9 @@ var ( MinPrice: minPrice, ExcessConversionConstant: excessConversionConstant, }, - expectedSeconds: 11, - expectedCost: math.MaxUint64, // Should not overflow - expectedExcess: math.MaxUint32 * 11, + expectedSeconds: 10, + expectedCost: 1_948_429_840_780_833_612, + expectedExcess: math.MaxUint32 * 10, }, } ) @@ -256,6 +256,63 @@ func TestStateCostOf(t *testing.T) { } } +func TestStateCostOfOverflow(t *testing.T) { + const target = 10_000 + config := Config{ + Target: target, + MinPrice: minPrice, + ExcessConversionConstant: excessConversionConstant, + } + + tests := []struct { + name string + state State + seconds uint64 + }{ + { + name: "current > target", + state: State{ + Current: math.MaxUint32, + Excess: 0, + }, + seconds: math.MaxUint64, + }, + { + name: "current == target", + state: State{ + Current: target, + Excess: 0, + }, + seconds: math.MaxUint64, + }, + { + name: "current < target", + state: State{ + Current: 0, + Excess: 0, + }, + seconds: math.MaxUint64, + }, + { + name: "current < target and reasonable excess", + state: State{ + Current: 0, + Excess: target + 1, + }, + seconds: math.MaxUint64/minPrice + 1, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require.Equal( + t, + uint64(math.MaxUint64), // Should not overflow + test.state.CostOf(config, test.seconds), + ) + }) + } +} + func TestStateSecondsUntil(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -268,6 +325,67 @@ func TestStateSecondsUntil(t *testing.T) { } } +func TestStateSecondsUntilLimit(t *testing.T) { + const target = 10_000 + tests := []struct { + name string + state State + minPrice gas.Price + costLimit uint64 + }{ + { + name: "zero price", + state: State{ + Current: math.MaxUint32, + Excess: 0, + }, + minPrice: 0, + costLimit: 0, + }, + { + name: "current > target", + state: State{ + Current: target + 1, + Excess: 0, + }, + minPrice: minPrice, + costLimit: math.MaxUint64, + }, + { + name: "current == target", + state: State{ + Current: target, + Excess: 0, + }, + minPrice: minPrice, + costLimit: minPrice * (week + 1), + }, + { + name: "current < target", + state: State{ + Current: 0, + Excess: 0, + }, + minPrice: minPrice, + costLimit: minPrice * (week + 1), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + config := Config{ + Target: target, + MinPrice: test.minPrice, + ExcessConversionConstant: excessConversionConstant, + } + require.Equal( + t, + uint64(week), + test.state.SecondsUntil(config, week, test.costLimit), + ) + }) + } +} + func BenchmarkStateCostOf(b *testing.B) { benchmarks := []struct { name string @@ -434,23 +552,17 @@ func (s State) unoptimizedCostOf(c Config, seconds uint64) uint64 { // unoptimizedSecondsUntil is a naive implementation of SecondsUntil that is // used for differential fuzzing. -func (s State) unoptimizedSecondsUntil(c Config, maxSeconds uint64, targetCost uint64) uint64 { - var ( - cost uint64 - seconds uint64 - err error - ) - for cost < targetCost && seconds < maxSeconds { +func (s State) unoptimizedSecondsUntil(c Config, maxSeconds uint64, costLimit uint64) uint64 { + for seconds := uint64(0); seconds < maxSeconds; seconds++ { s = s.AdvanceTime(c.Target, 1) - seconds++ - price := gas.CalculatePrice(c.MinPrice, s.Excess, c.ExcessConversionConstant) - cost, err = safemath.Add(cost, uint64(price)) - if err != nil { + price := uint64(gas.CalculatePrice(c.MinPrice, s.Excess, c.ExcessConversionConstant)) + if price > costLimit { return seconds } + costLimit -= price } - return seconds + return maxSeconds } // floatToGas converts f to gas.Gas by truncation. `gas.Gas(f)` is preferred and From a32cec5793584f60dd8fe0049237c1c2becdd94c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 17 Sep 2024 23:02:36 -0400 Subject: [PATCH 048/199] Prevent flaky test --- vms/platformvm/validators/fee/fee.go | 8 ++--- vms/platformvm/validators/fee/fee_test.go | 43 +++++++++++------------ 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/vms/platformvm/validators/fee/fee.go b/vms/platformvm/validators/fee/fee.go index 16c9c35cfac6..503cd4a700ef 100644 --- a/vms/platformvm/validators/fee/fee.go +++ b/vms/platformvm/validators/fee/fee.go @@ -91,10 +91,10 @@ func (s State) CostOf(c Config, seconds uint64) uint64 { return cost } -// SecondsUntil calculates the maximum number of seconds that a validator can -// pay fees before their [costLimit] would be exceeded based on the dynamic fee -// mechanism. The result is capped at [maxSeconds]. -func (s State) SecondsUntil(c Config, maxSeconds uint64, costLimit uint64) uint64 { +// SecondsRemaining calculates the maximum number of seconds that a validator +// can pay fees before their [costLimit] would be exceeded based on the dynamic +// fee mechanism. The result is capped at [maxSeconds]. +func (s State) SecondsRemaining(c Config, maxSeconds uint64, costLimit uint64) uint64 { // Because this function can divide by prices, we need to sanity check the // parameters to avoid division by 0. if c.MinPrice == 0 { diff --git a/vms/platformvm/validators/fee/fee_test.go b/vms/platformvm/validators/fee/fee_test.go index 602050d72c6b..ccc710f1c7f5 100644 --- a/vms/platformvm/validators/fee/fee_test.go +++ b/vms/platformvm/validators/fee/fee_test.go @@ -20,7 +20,6 @@ const ( hour = 60 * minute day = 24 * hour week = 7 * day - year = 365 * day minPrice = 2_048 @@ -313,19 +312,19 @@ func TestStateCostOfOverflow(t *testing.T) { } } -func TestStateSecondsUntil(t *testing.T) { +func TestStateSecondsRemaining(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { require.Equal( t, test.expectedSeconds, - test.state.SecondsUntil(test.config, year, test.expectedCost), + test.state.SecondsRemaining(test.config, week, test.expectedCost), ) }) } } -func TestStateSecondsUntilLimit(t *testing.T) { +func TestStateSecondsRemainingLimit(t *testing.T) { const target = 10_000 tests := []struct { name string @@ -380,7 +379,7 @@ func TestStateSecondsUntilLimit(t *testing.T) { require.Equal( t, uint64(week), - test.state.SecondsUntil(config, week, test.costLimit), + test.state.SecondsRemaining(config, week, test.costLimit), ) }) } @@ -417,10 +416,10 @@ func BenchmarkStateCostOf(b *testing.B) { } } -func BenchmarkStateSecondsUntil(b *testing.B) { +func BenchmarkStateSecondsRemaining(b *testing.B) { benchmarks := []struct { - name string - secondsUntil func( + name string + secondsRemaining func( s State, c Config, maxSeconds uint64, @@ -428,12 +427,12 @@ func BenchmarkStateSecondsUntil(b *testing.B) { ) uint64 }{ { - name: "unoptimized", - secondsUntil: State.unoptimizedSecondsUntil, + name: "unoptimized", + secondsRemaining: State.unoptimizedSecondsRemaining, }, { - name: "optimized", - secondsUntil: State.SecondsUntil, + name: "optimized", + secondsRemaining: State.SecondsRemaining, }, } for _, test := range tests { @@ -441,7 +440,7 @@ func BenchmarkStateSecondsUntil(b *testing.B) { for _, benchmark := range benchmarks { b.Run(benchmark.name, func(b *testing.B) { for i := 0; i < b.N; i++ { - benchmark.secondsUntil(test.state, test.config, year, test.expectedCost) + benchmark.secondsRemaining(test.state, test.config, week, test.expectedCost) } }) } @@ -479,7 +478,7 @@ func FuzzStateCostOf(f *testing.F) { MinPrice: gas.Price(minPrice), ExcessConversionConstant: gas.Gas(max(excessConversionConstant, 1)), } - seconds = min(seconds, year) + seconds = min(seconds, week) require.Equal( t, s.unoptimizedCostOf(c, seconds), @@ -489,7 +488,7 @@ func FuzzStateCostOf(f *testing.F) { ) } -func FuzzStateSecondsUntil(f *testing.F) { +func FuzzStateSecondsRemaining(f *testing.F) { for _, test := range tests { f.Add( uint64(test.state.Current), @@ -497,7 +496,7 @@ func FuzzStateSecondsUntil(f *testing.F) { uint64(test.config.Target), uint64(test.config.MinPrice), uint64(test.config.ExcessConversionConstant), - uint64(year), + uint64(week), test.expectedCost, ) } @@ -521,11 +520,11 @@ func FuzzStateSecondsUntil(f *testing.F) { MinPrice: gas.Price(minPrice), ExcessConversionConstant: gas.Gas(max(excessConversionConstant, 1)), } - maxSeconds = min(maxSeconds, year) + maxSeconds = min(maxSeconds, week) require.Equal( t, - s.unoptimizedSecondsUntil(c, maxSeconds, targetCost), - s.SecondsUntil(c, maxSeconds, targetCost), + s.unoptimizedSecondsRemaining(c, maxSeconds, targetCost), + s.SecondsRemaining(c, maxSeconds, targetCost), ) }, ) @@ -550,9 +549,9 @@ func (s State) unoptimizedCostOf(c Config, seconds uint64) uint64 { return cost } -// unoptimizedSecondsUntil is a naive implementation of SecondsUntil that is -// used for differential fuzzing. -func (s State) unoptimizedSecondsUntil(c Config, maxSeconds uint64, costLimit uint64) uint64 { +// unoptimizedSecondsRemaining is a naive implementation of SecondsRemaining +// that is used for differential fuzzing. +func (s State) unoptimizedSecondsRemaining(c Config, maxSeconds uint64, costLimit uint64) uint64 { for seconds := uint64(0); seconds < maxSeconds; seconds++ { s = s.AdvanceTime(c.Target, 1) From eb62df7e853a5fe83ad1573bdb51b1644e5aae28 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 18 Sep 2024 00:48:28 -0400 Subject: [PATCH 049/199] Implement acp-77 fee configs --- config/config.go | 12 +++- config/flags.go | 5 ++ config/keys.go | 118 +++++++++++++++++--------------- genesis/genesis_fuji.go | 12 +++- genesis/genesis_local.go | 12 +++- genesis/genesis_mainnet.go | 12 +++- genesis/params.go | 12 ++-- node/node.go | 2 + vms/platformvm/config/config.go | 14 ++-- 9 files changed, 126 insertions(+), 73 deletions(-) diff --git a/config/config.go b/config/config.go index 4d7937086073..46fd0aac8af5 100644 --- a/config/config.go +++ b/config/config.go @@ -47,8 +47,10 @@ import ( "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/reward" - "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "github.com/ava-labs/avalanchego/vms/proposervm" + + txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) const ( @@ -768,7 +770,7 @@ func getTxFeeConfig(v *viper.Viper, networkID uint32) genesis.TxFeeConfig { if networkID != constants.MainnetID && networkID != constants.FujiID { return genesis.TxFeeConfig{ CreateAssetTxFee: v.GetUint64(CreateAssetTxFeeKey), - StaticFeeConfig: fee.StaticConfig{ + StaticFeeConfig: txfee.StaticConfig{ TxFee: v.GetUint64(TxFeeKey), CreateSubnetTxFee: v.GetUint64(CreateSubnetTxFeeKey), TransformSubnetTxFee: v.GetUint64(TransformSubnetTxFeeKey), @@ -791,6 +793,12 @@ func getTxFeeConfig(v *viper.Viper, networkID uint32) genesis.TxFeeConfig { MinPrice: gas.Price(v.GetUint64(DynamicFeesMinGasPriceKey)), ExcessConversionConstant: gas.Gas(v.GetUint64(DynamicFeesExcessConversionConstantKey)), }, + ValidatorFeeCapacity: gas.Gas(v.GetUint64(ValidatorFeesCapacityKey)), + ValidatorFeeConfig: validatorfee.Config{ + Target: gas.Gas(v.GetUint64(ValidatorFeesTargetKey)), + MinPrice: gas.Price(v.GetUint64(ValidatorFeesMinPriceKey)), + ExcessConversionConstant: gas.Gas(v.GetUint64(ValidatorFeesExcessConversionConstantKey)), + }, } } return genesis.GetTxFeeConfig(networkID) diff --git a/config/flags.go b/config/flags.go index 9f092c563b32..db89944e357d 100644 --- a/config/flags.go +++ b/config/flags.go @@ -105,6 +105,11 @@ func addNodeFlags(fs *pflag.FlagSet) { fs.IntSlice(ACPObjectKey, nil, "ACPs to object adoption") // AVAX fees: + // Validator fees: + fs.Uint64(ValidatorFeesCapacityKey, uint64(genesis.LocalParams.ValidatorFeeCapacity), "Maximum number of validators") + fs.Uint64(ValidatorFeesTargetKey, uint64(genesis.LocalParams.ValidatorFeeConfig.Target), "Target number of validators") + fs.Uint64(ValidatorFeesMinPriceKey, uint64(genesis.LocalParams.ValidatorFeeConfig.MinPrice), "Minimum validator price per second") + fs.Uint64(ValidatorFeesExcessConversionConstantKey, uint64(genesis.LocalParams.ValidatorFeeConfig.ExcessConversionConstant), "Constant to convert validator excess price") // Dynamic fees: fs.Uint64(DynamicFeesBandwidthWeightKey, genesis.LocalParams.DynamicFeeConfig.Weights[gas.Bandwidth], "Complexity multiplier used to convert Bandwidth into Gas") fs.Uint64(DynamicFeesDBReadWeightKey, genesis.LocalParams.DynamicFeeConfig.Weights[gas.DBRead], "Complexity multiplier used to convert DB Reads into Gas") diff --git a/config/keys.go b/config/keys.go index 714136a74073..dcea2621f6ff 100644 --- a/config/keys.go +++ b/config/keys.go @@ -8,63 +8,67 @@ package config const HTTPWriteTimeoutKey = "http-write-timeout" // #nosec G101 const ( - DataDirKey = "data-dir" - ConfigFileKey = "config-file" - ConfigContentKey = "config-file-content" - ConfigContentTypeKey = "config-file-content-type" - VersionKey = "version" - VersionJSONKey = "version-json" - GenesisFileKey = "genesis-file" - GenesisFileContentKey = "genesis-file-content" - UpgradeFileKey = "upgrade-file" - UpgradeFileContentKey = "upgrade-file-content" - NetworkNameKey = "network-id" - ACPSupportKey = "acp-support" - ACPObjectKey = "acp-object" - DynamicFeesBandwidthWeightKey = "dynamic-fees-bandwidth-weight" - DynamicFeesDBReadWeightKey = "dynamic-fees-db-read-weight" - DynamicFeesDBWriteWeightKey = "dynamic-fees-db-write-weight" - DynamicFeesComputeWeightKey = "dynamic-fees-compute-weight" - DynamicFeesMaxGasCapacityKey = "dynamic-fees-max-gas-capacity" - DynamicFeesMaxGasPerSecondKey = "dynamic-fees-max-gas-per-second" - DynamicFeesTargetGasPerSecondKey = "dynamic-fees-target-gas-per-second" - DynamicFeesMinGasPriceKey = "dynamic-fees-min-gas-price" - DynamicFeesExcessConversionConstantKey = "dynamic-fees-excess-conversion-constant" - TxFeeKey = "tx-fee" - CreateAssetTxFeeKey = "create-asset-tx-fee" - CreateSubnetTxFeeKey = "create-subnet-tx-fee" - TransformSubnetTxFeeKey = "transform-subnet-tx-fee" - CreateBlockchainTxFeeKey = "create-blockchain-tx-fee" - AddPrimaryNetworkValidatorFeeKey = "add-primary-network-validator-fee" - AddPrimaryNetworkDelegatorFeeKey = "add-primary-network-delegator-fee" - AddSubnetValidatorFeeKey = "add-subnet-validator-fee" - AddSubnetDelegatorFeeKey = "add-subnet-delegator-fee" - UptimeRequirementKey = "uptime-requirement" - MinValidatorStakeKey = "min-validator-stake" - MaxValidatorStakeKey = "max-validator-stake" - MinDelegatorStakeKey = "min-delegator-stake" - MinDelegatorFeeKey = "min-delegation-fee" - MinStakeDurationKey = "min-stake-duration" - MaxStakeDurationKey = "max-stake-duration" - StakeMaxConsumptionRateKey = "stake-max-consumption-rate" - StakeMinConsumptionRateKey = "stake-min-consumption-rate" - StakeMintingPeriodKey = "stake-minting-period" - StakeSupplyCapKey = "stake-supply-cap" - DBTypeKey = "db-type" - DBReadOnlyKey = "db-read-only" - DBPathKey = "db-dir" - DBConfigFileKey = "db-config-file" - DBConfigContentKey = "db-config-file-content" - PublicIPKey = "public-ip" - PublicIPResolutionFreqKey = "public-ip-resolution-frequency" - PublicIPResolutionServiceKey = "public-ip-resolution-service" - HTTPHostKey = "http-host" - HTTPPortKey = "http-port" - HTTPSEnabledKey = "http-tls-enabled" - HTTPSKeyFileKey = "http-tls-key-file" - HTTPSKeyContentKey = "http-tls-key-file-content" - HTTPSCertFileKey = "http-tls-cert-file" - HTTPSCertContentKey = "http-tls-cert-file-content" + DataDirKey = "data-dir" + ConfigFileKey = "config-file" + ConfigContentKey = "config-file-content" + ConfigContentTypeKey = "config-file-content-type" + VersionKey = "version" + VersionJSONKey = "version-json" + GenesisFileKey = "genesis-file" + GenesisFileContentKey = "genesis-file-content" + UpgradeFileKey = "upgrade-file" + UpgradeFileContentKey = "upgrade-file-content" + NetworkNameKey = "network-id" + ACPSupportKey = "acp-support" + ACPObjectKey = "acp-object" + DynamicFeesBandwidthWeightKey = "dynamic-fees-bandwidth-weight" + DynamicFeesDBReadWeightKey = "dynamic-fees-db-read-weight" + DynamicFeesDBWriteWeightKey = "dynamic-fees-db-write-weight" + DynamicFeesComputeWeightKey = "dynamic-fees-compute-weight" + DynamicFeesMaxGasCapacityKey = "dynamic-fees-max-gas-capacity" + DynamicFeesMaxGasPerSecondKey = "dynamic-fees-max-gas-per-second" + DynamicFeesTargetGasPerSecondKey = "dynamic-fees-target-gas-per-second" + DynamicFeesMinGasPriceKey = "dynamic-fees-min-gas-price" + DynamicFeesExcessConversionConstantKey = "dynamic-fees-excess-conversion-constant" + ValidatorFeesCapacityKey = "validator-fees-capacity" + ValidatorFeesTargetKey = "validator-fees-target" + ValidatorFeesMinPriceKey = "validator-fees-min-price" + ValidatorFeesExcessConversionConstantKey = "validator-fees-excess-conversion-constant" + TxFeeKey = "tx-fee" + CreateAssetTxFeeKey = "create-asset-tx-fee" + CreateSubnetTxFeeKey = "create-subnet-tx-fee" + TransformSubnetTxFeeKey = "transform-subnet-tx-fee" + CreateBlockchainTxFeeKey = "create-blockchain-tx-fee" + AddPrimaryNetworkValidatorFeeKey = "add-primary-network-validator-fee" + AddPrimaryNetworkDelegatorFeeKey = "add-primary-network-delegator-fee" + AddSubnetValidatorFeeKey = "add-subnet-validator-fee" + AddSubnetDelegatorFeeKey = "add-subnet-delegator-fee" + UptimeRequirementKey = "uptime-requirement" + MinValidatorStakeKey = "min-validator-stake" + MaxValidatorStakeKey = "max-validator-stake" + MinDelegatorStakeKey = "min-delegator-stake" + MinDelegatorFeeKey = "min-delegation-fee" + MinStakeDurationKey = "min-stake-duration" + MaxStakeDurationKey = "max-stake-duration" + StakeMaxConsumptionRateKey = "stake-max-consumption-rate" + StakeMinConsumptionRateKey = "stake-min-consumption-rate" + StakeMintingPeriodKey = "stake-minting-period" + StakeSupplyCapKey = "stake-supply-cap" + DBTypeKey = "db-type" + DBReadOnlyKey = "db-read-only" + DBPathKey = "db-dir" + DBConfigFileKey = "db-config-file" + DBConfigContentKey = "db-config-file-content" + PublicIPKey = "public-ip" + PublicIPResolutionFreqKey = "public-ip-resolution-frequency" + PublicIPResolutionServiceKey = "public-ip-resolution-service" + HTTPHostKey = "http-host" + HTTPPortKey = "http-port" + HTTPSEnabledKey = "http-tls-enabled" + HTTPSKeyFileKey = "http-tls-key-file" + HTTPSKeyContentKey = "http-tls-key-file-content" + HTTPSCertFileKey = "http-tls-cert-file" + HTTPSCertContentKey = "http-tls-cert-file-content" HTTPAllowedOrigins = "http-allowed-origins" HTTPAllowedHostsKey = "http-allowed-hosts" diff --git a/genesis/genesis_fuji.go b/genesis/genesis_fuji.go index 3d59bb725756..32a6c0403369 100644 --- a/genesis/genesis_fuji.go +++ b/genesis/genesis_fuji.go @@ -11,7 +11,9 @@ import ( "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/reward" - "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + + txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) var ( @@ -22,7 +24,7 @@ var ( FujiParams = Params{ TxFeeConfig: TxFeeConfig{ CreateAssetTxFee: 10 * units.MilliAvax, - StaticFeeConfig: fee.StaticConfig{ + StaticFeeConfig: txfee.StaticConfig{ TxFee: units.MilliAvax, CreateSubnetTxFee: 100 * units.MilliAvax, TransformSubnetTxFee: 1 * units.Avax, @@ -46,6 +48,12 @@ var ( MinPrice: 1, ExcessConversionConstant: 5_000, }, + ValidatorFeeCapacity: 20_000, + ValidatorFeeConfig: validatorfee.Config{ + Target: 10_000, + MinPrice: 512, + ExcessConversionConstant: 51_937_021, // Double every hour + }, }, StakingConfig: StakingConfig{ UptimeRequirement: .8, // 80% diff --git a/genesis/genesis_local.go b/genesis/genesis_local.go index d3ce77aec961..b0e6cf57bd42 100644 --- a/genesis/genesis_local.go +++ b/genesis/genesis_local.go @@ -14,7 +14,9 @@ import ( "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/reward" - "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + + txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) // PrivateKey-vmRQiZeXEXYMyJhEiqdC2z5JhuDbxL8ix9UVvjgMu2Er1NepE => P-local1g65uqn6t77p656w64023nh8nd9updzmxyymev2 @@ -40,7 +42,7 @@ var ( LocalParams = Params{ TxFeeConfig: TxFeeConfig{ CreateAssetTxFee: units.MilliAvax, - StaticFeeConfig: fee.StaticConfig{ + StaticFeeConfig: txfee.StaticConfig{ TxFee: units.MilliAvax, CreateSubnetTxFee: 100 * units.MilliAvax, TransformSubnetTxFee: 100 * units.MilliAvax, @@ -64,6 +66,12 @@ var ( MinPrice: 1, ExcessConversionConstant: 5_000, }, + ValidatorFeeCapacity: 20_000, + ValidatorFeeConfig: validatorfee.Config{ + Target: 10_000, + MinPrice: 1, + ExcessConversionConstant: 865_617, // Double every minute + }, }, StakingConfig: StakingConfig{ UptimeRequirement: .8, // 80% diff --git a/genesis/genesis_mainnet.go b/genesis/genesis_mainnet.go index 8d4a6d4f777f..68457b6776b2 100644 --- a/genesis/genesis_mainnet.go +++ b/genesis/genesis_mainnet.go @@ -11,7 +11,9 @@ import ( "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/reward" - "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + + txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) var ( @@ -22,7 +24,7 @@ var ( MainnetParams = Params{ TxFeeConfig: TxFeeConfig{ CreateAssetTxFee: 10 * units.MilliAvax, - StaticFeeConfig: fee.StaticConfig{ + StaticFeeConfig: txfee.StaticConfig{ TxFee: units.MilliAvax, CreateSubnetTxFee: 1 * units.Avax, TransformSubnetTxFee: 10 * units.Avax, @@ -46,6 +48,12 @@ var ( MinPrice: 1, ExcessConversionConstant: 5_000, }, + ValidatorFeeCapacity: 20_000, + ValidatorFeeConfig: validatorfee.Config{ + Target: 10_000, + MinPrice: 512, + ExcessConversionConstant: 1_246_488_515, // Double every day + }, }, StakingConfig: StakingConfig{ UptimeRequirement: .8, // 80% diff --git a/genesis/params.go b/genesis/params.go index e51af8b81c1b..43c2f1c1301c 100644 --- a/genesis/params.go +++ b/genesis/params.go @@ -9,7 +9,9 @@ import ( "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/reward" - "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + + txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) type StakingConfig struct { @@ -36,9 +38,11 @@ type StakingConfig struct { } type TxFeeConfig struct { - CreateAssetTxFee uint64 `json:"createAssetTxFee"` - StaticFeeConfig fee.StaticConfig `json:"staticFeeConfig"` - DynamicFeeConfig gas.Config `json:"dynamicFeeConfig"` + CreateAssetTxFee uint64 `json:"createAssetTxFee"` + StaticFeeConfig txfee.StaticConfig `json:"staticFeeConfig"` + DynamicFeeConfig gas.Config `json:"dynamicFeeConfig"` + ValidatorFeeCapacity gas.Gas `json:"validatorFeeCapacity"` + ValidatorFeeConfig validatorfee.Config `json:"validatorFeeConfig"` } type Params struct { diff --git a/node/node.go b/node/node.go index b2aba9314439..5f6ef111858a 100644 --- a/node/node.go +++ b/node/node.go @@ -1228,6 +1228,8 @@ func (n *Node) initVMs() error { CreateAssetTxFee: n.Config.CreateAssetTxFee, StaticFeeConfig: n.Config.StaticFeeConfig, DynamicFeeConfig: n.Config.DynamicFeeConfig, + ValidatorFeeCapacity: n.Config.ValidatorFeeCapacity, + ValidatorFeeConfig: n.Config.ValidatorFeeConfig, UptimePercentage: n.Config.UptimeRequirement, MinValidatorStake: n.Config.MinValidatorStake, MaxValidatorStake: n.Config.MaxValidatorStake, diff --git a/vms/platformvm/config/config.go b/vms/platformvm/config/config.go index 406285f0e59c..f8bda923455c 100644 --- a/vms/platformvm/config/config.go +++ b/vms/platformvm/config/config.go @@ -16,7 +16,9 @@ import ( "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/reward" "github.com/ava-labs/avalanchego/vms/platformvm/txs" - "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + + txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) // Struct collecting all foundational parameters of PlatformVM @@ -32,13 +34,17 @@ type Config struct { // calling VM.Initialize. Validators validators.Manager - // Static fees are active before the E-upgrade + // Static fees are active before Etna CreateAssetTxFee uint64 // Override for CreateSubnet and CreateChain before AP3 - StaticFeeConfig fee.StaticConfig + StaticFeeConfig txfee.StaticConfig - // Dynamic fees are active after the E-upgrade + // Dynamic fees are active after Etna DynamicFeeConfig gas.Config + // ACP-77 validator fees are active after Etna + ValidatorFeeCapacity gas.Gas + ValidatorFeeConfig validatorfee.Config + // Provides access to the uptime manager as a thread safe data structure UptimeLockedCalculator uptime.LockedCalculator From 6b7a68509769722f421a4821250fc5253c3387d0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 18 Sep 2024 01:38:12 -0400 Subject: [PATCH 050/199] Add initial validators to convertSubnetTx --- vms/platformvm/txs/convert_subnet_tx.go | 43 ++++++++++++++-- .../txs/executor/standard_tx_executor.go | 50 ++++++++++++++++++- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/txs/convert_subnet_tx.go b/vms/platformvm/txs/convert_subnet_tx.go index 3fa4193faf3c..4707bb103f01 100644 --- a/vms/platformvm/txs/convert_subnet_tx.go +++ b/vms/platformvm/txs/convert_subnet_tx.go @@ -10,16 +10,21 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/ava-labs/avalanchego/vms/platformvm/fx" + "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/types" ) const MaxSubnetAddressLength = 4096 var ( - _ UnsignedTx = (*TransferSubnetOwnershipTx)(nil) + _ UnsignedTx = (*ConvertSubnetTx)(nil) - ErrConvertPermissionlessSubnet = errors.New("cannot convert a permissionless subnet") - ErrAddressTooLong = errors.New("address is too long") + ErrConvertPermissionlessSubnet = errors.New("cannot convert a permissionless subnet") + ErrAddressTooLong = errors.New("address is too long") + ErrConvertMustIncludeValidators = errors.New("conversion must include at least one validator") + ErrZeroWeight = errors.New("validator weight must be non-zero") + ErrMissingPublicKey = errors.New("missing public key") ) type ConvertSubnetTx struct { @@ -31,6 +36,8 @@ type ConvertSubnetTx struct { ChainID ids.ID `serialize:"true" json:"chainID"` // Address of the Subnet manager Address types.JSONByteSlice `serialize:"true" json:"address"` + // Initial pay-as-you-go validators for the Subnet + Validators []ConvertSubnetValidator `serialize:"true" json:"validators"` // Authorizes this conversion SubnetAuth verify.Verifiable `serialize:"true" json:"subnetAuthorization"` } @@ -46,11 +53,24 @@ func (tx *ConvertSubnetTx) SyntacticVerify(ctx *snow.Context) error { return ErrConvertPermissionlessSubnet case len(tx.Address) > MaxSubnetAddressLength: return ErrAddressTooLong + case len(tx.Validators) == 0: + return ErrConvertMustIncludeValidators } if err := tx.BaseTx.SyntacticVerify(ctx); err != nil { return err } + for _, vdr := range tx.Validators { + if vdr.Weight == 0 { + return ErrZeroWeight + } + if err := verify.All(vdr.Signer, vdr.RemainingBalanceOwner); err != nil { + return err + } + if vdr.Signer.Key() == nil { + return ErrMissingPublicKey + } + } if err := tx.SubnetAuth.Verify(); err != nil { return err } @@ -62,3 +82,20 @@ func (tx *ConvertSubnetTx) SyntacticVerify(ctx *snow.Context) error { func (tx *ConvertSubnetTx) Visit(visitor Visitor) error { return visitor.ConvertSubnetTx(tx) } + +type ConvertSubnetValidator struct { + // TODO: Must be Ed25519 NodeID + NodeID ids.NodeID `json:"nodeID"` + // Weight of this validator used when sampling + Weight uint64 `json:"weight"` + // Initial balance for this validator + Balance uint64 `json:"balance"` + // [Signer] is the BLS key for this validator. + // Note: We do not enforce that the BLS key is unique across all validators. + // This means that validators can share a key if they so choose. + // However, a NodeID + Subnet does uniquely map to a BLS key + Signer signer.Signer `json:"signer"` + // Leftover $AVAX from the [Balance] will be issued to this owner once it is + // removed from the validator set. + RemainingBalanceOwner fx.Owner `json:"remainingBalanceOwner"` +} diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index d0a37f5ba82c..e2669c3510e4 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -14,8 +14,11 @@ import ( "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/components/verify" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" @@ -30,6 +33,7 @@ var ( errMissingStartTimePreDurango = errors.New("staker transactions must have a StartTime pre-Durango") errEtnaUpgradeNotActive = errors.New("attempting to use an Etna-upgrade feature prior to activation") errTransformSubnetTxPostEtna = errors.New("TransformSubnetTx is not permitted post-Etna") + errMaxNumActiveValidators = errors.New("already at the max number of active validators") ) type StandardTxExecutor struct { @@ -522,6 +526,50 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { if err != nil { return err } + + var ( + txID = e.Tx.ID() + startTime = uint64(currentTimestamp.Unix()) + currentFees = e.State.GetAccruedFees() + ) + for i, vdr := range tx.Validators { + vdr := vdr + balanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &vdr.RemainingBalanceOwner) + if err != nil { + return err + } + + sov := state.SubnetOnlyValidator{ + ValidationID: txID.Prefix(uint64(i)), + SubnetID: tx.Subnet, + NodeID: vdr.NodeID, + PublicKey: bls.PublicKeyToUncompressedBytes(vdr.Signer.Key()), + RemainingBalanceOwner: balanceOwner, + StartTime: startTime, + Weight: vdr.Weight, + MinNonce: 0, + EndAccumulatedFee: 0, // If Balance is 0, this is 0 + } + if vdr.Balance != 0 { + if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeCapacity { + return errMaxNumActiveValidators + } + + sov.EndAccumulatedFee, err = math.Add(vdr.Balance, currentFees) + if err != nil { + return err + } + + fee, err = math.Add(fee, vdr.Balance) + if err != nil { + return err + } + } + + if err := e.State.PutSubnetOnlyValidator(sov); err != nil { + return err + } + } if err := e.Backend.FlowChecker.VerifySpend( tx, e.State, @@ -535,8 +583,6 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { return err } - txID := e.Tx.ID() - // Consume the UTXOS avax.Consume(e.State, tx.Ins) // Produce the UTXOS From 5a6d1b654f5ea207bd89a5ba8a5ffe353922b2f3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 18 Sep 2024 11:31:47 -0400 Subject: [PATCH 051/199] Add additional checks --- vms/platformvm/txs/convert_subnet_tx.go | 31 ++- vms/platformvm/txs/convert_subnet_tx_test.go | 197 +++++++++++++++++- .../txs/convert_subnet_tx_test_complex.json | 18 ++ .../txs/convert_subnet_tx_test_simple.json | 1 + vms/platformvm/txs/fee/complexity.go | 46 ++++ wallet/chain/p/builder/builder.go | 22 +- .../chain/p/builder/builder_with_options.go | 2 + wallet/chain/p/builder_test.go | 38 +++- wallet/chain/p/wallet/wallet.go | 4 +- wallet/chain/p/wallet/with_options.go | 2 + 10 files changed, 340 insertions(+), 21 deletions(-) diff --git a/vms/platformvm/txs/convert_subnet_tx.go b/vms/platformvm/txs/convert_subnet_tx.go index 4707bb103f01..34ed7996ccc6 100644 --- a/vms/platformvm/txs/convert_subnet_tx.go +++ b/vms/platformvm/txs/convert_subnet_tx.go @@ -8,6 +8,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/vms/components/verify" "github.com/ava-labs/avalanchego/vms/platformvm/fx" @@ -18,13 +19,15 @@ import ( const MaxSubnetAddressLength = 4096 var ( - _ UnsignedTx = (*ConvertSubnetTx)(nil) + _ UnsignedTx = (*ConvertSubnetTx)(nil) + _ utils.Sortable[ConvertSubnetValidator] = ConvertSubnetValidator{} - ErrConvertPermissionlessSubnet = errors.New("cannot convert a permissionless subnet") - ErrAddressTooLong = errors.New("address is too long") - ErrConvertMustIncludeValidators = errors.New("conversion must include at least one validator") - ErrZeroWeight = errors.New("validator weight must be non-zero") - ErrMissingPublicKey = errors.New("missing public key") + ErrConvertPermissionlessSubnet = errors.New("cannot convert a permissionless subnet") + ErrAddressTooLong = errors.New("address is too long") + ErrConvertMustIncludeValidators = errors.New("conversion must include at least one validator") + ErrConvertValidatorsNotSortedAndUnique = errors.New("conversion validators must be sorted and unique") + ErrZeroWeight = errors.New("validator weight must be non-zero") + ErrMissingPublicKey = errors.New("missing public key") ) type ConvertSubnetTx struct { @@ -55,6 +58,8 @@ func (tx *ConvertSubnetTx) SyntacticVerify(ctx *snow.Context) error { return ErrAddressTooLong case len(tx.Validators) == 0: return ErrConvertMustIncludeValidators + case !utils.IsSortedAndUnique(tx.Validators): + return ErrConvertValidatorsNotSortedAndUnique } if err := tx.BaseTx.SyntacticVerify(ctx); err != nil { @@ -85,17 +90,21 @@ func (tx *ConvertSubnetTx) Visit(visitor Visitor) error { type ConvertSubnetValidator struct { // TODO: Must be Ed25519 NodeID - NodeID ids.NodeID `json:"nodeID"` + NodeID ids.NodeID `serialize:"true" json:"nodeID"` // Weight of this validator used when sampling - Weight uint64 `json:"weight"` + Weight uint64 `serialize:"true" json:"weight"` // Initial balance for this validator - Balance uint64 `json:"balance"` + Balance uint64 `serialize:"true" json:"balance"` // [Signer] is the BLS key for this validator. // Note: We do not enforce that the BLS key is unique across all validators. // This means that validators can share a key if they so choose. // However, a NodeID + Subnet does uniquely map to a BLS key - Signer signer.Signer `json:"signer"` + Signer signer.Signer `serialize:"true" json:"signer"` // Leftover $AVAX from the [Balance] will be issued to this owner once it is // removed from the validator set. - RemainingBalanceOwner fx.Owner `json:"remainingBalanceOwner"` + RemainingBalanceOwner fx.Owner `serialize:"true" json:"remainingBalanceOwner"` +} + +func (v ConvertSubnetValidator) Compare(o ConvertSubnetValidator) int { + return v.NodeID.Compare(o.NodeID) } diff --git a/vms/platformvm/txs/convert_subnet_tx_test.go b/vms/platformvm/txs/convert_subnet_tx_test.go index 42a392f0181e..3d122d5c9cf4 100644 --- a/vms/platformvm/txs/convert_subnet_tx_test.go +++ b/vms/platformvm/txs/convert_subnet_tx_test.go @@ -4,6 +4,7 @@ package txs import ( + "encoding/hex" "encoding/json" "strings" "testing" @@ -15,8 +16,10 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/platformvm/stakeable" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/vms/types" @@ -30,6 +33,11 @@ var ( ) func TestConvertSubnetTxSerialization(t *testing.T) { + skBytes, err := hex.DecodeString("6668fecd4595b81e4d568398c820bbf3f073cb222902279fa55ebb84764ed2e3") + require.NoError(t, err) + sk, err := bls.SecretKeyFromBytes(skBytes) + require.NoError(t, err) + var ( addr = ids.ShortID{ 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, @@ -71,6 +79,11 @@ func TestConvertSubnetTxSerialization(t *testing.T) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, } + nodeID = ids.BuildTestNodeID([]byte{ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x11, 0x22, 0x33, 0x44, + }) ) tests := []struct { @@ -107,9 +120,10 @@ func TestConvertSubnetTxSerialization(t *testing.T) { Memo: types.JSONByteSlice{}, }, }, - Subnet: subnetID, - ChainID: managerChainID, - Address: managerAddress, + Subnet: subnetID, + ChainID: managerChainID, + Address: managerAddress, + Validators: []ConvertSubnetValidator{}, SubnetAuth: &secp256k1fx.Input{ SigIndices: []uint32{3}, }, @@ -169,6 +183,8 @@ func TestConvertSubnetTxSerialization(t *testing.T) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, + // number of validators + 0x00, 0x00, 0x00, 0x00, // secp256k1fx authorization type ID 0x00, 0x00, 0x00, 0x0a, // number of signatures needed in authorization @@ -277,6 +293,21 @@ func TestConvertSubnetTxSerialization(t *testing.T) { Subnet: subnetID, ChainID: managerChainID, Address: managerAddress, + Validators: []ConvertSubnetValidator{ + { + NodeID: nodeID, + Weight: 0x0102030405060708, + Balance: units.Avax, + Signer: signer.NewProofOfPossession(sk), + RemainingBalanceOwner: &secp256k1fx.OutputOwners{ + Locktime: 0, + Threshold: 1, + Addrs: []ids.ShortID{ + addr, + }, + }, + }, + }, SubnetAuth: &secp256k1fx.Input{ SigIndices: []uint32{}, }, @@ -430,6 +461,51 @@ func TestConvertSubnetTxSerialization(t *testing.T) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, + // number of validators + 0x00, 0x00, 0x00, 0x01, + // Validators[0] + // node ID + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x11, 0x22, 0x33, 0x44, + // weight + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + // balance + 0x00, 0x00, 0x00, 0x00, 0x3b, 0x9a, 0xca, 0x00, + // signer type ID + 0x00, 0x00, 0x00, 0x1c, + // BLS compressed public key + 0xaf, 0xf4, 0xac, 0xb4, 0xc5, 0x43, 0x9b, 0x5d, + 0x42, 0x6c, 0xad, 0xf9, 0xe9, 0x46, 0xd3, 0xa4, + 0x52, 0xf7, 0xde, 0x34, 0x14, 0xd1, 0xad, 0x27, + 0x33, 0x61, 0x33, 0x21, 0x1d, 0x8b, 0x90, 0xcf, + 0x49, 0xfb, 0x97, 0xee, 0xbc, 0xde, 0xee, 0xf7, + 0x14, 0xdc, 0x20, 0xf5, 0x4e, 0xd0, 0xd4, 0xd1, + // BLS compressed signature + 0x8c, 0xfd, 0x79, 0x09, 0xd1, 0x53, 0xb9, 0x60, + 0x4b, 0x62, 0xb1, 0x43, 0xba, 0x36, 0x20, 0x7b, + 0xb7, 0xe6, 0x48, 0x67, 0x42, 0x44, 0x80, 0x20, + 0x2a, 0x67, 0xdc, 0x68, 0x76, 0x83, 0x46, 0xd9, + 0x5c, 0x90, 0x98, 0x3c, 0x2d, 0x27, 0x9c, 0x64, + 0xc4, 0x3c, 0x51, 0x13, 0x6b, 0x2a, 0x05, 0xe0, + 0x16, 0x02, 0xd5, 0x2a, 0xa6, 0x37, 0x6f, 0xda, + 0x17, 0xfa, 0x6e, 0x2a, 0x18, 0xa0, 0x83, 0xe4, + 0x9d, 0x9c, 0x45, 0x0e, 0xab, 0x7b, 0x89, 0xb1, + 0xd5, 0x55, 0x5d, 0xa5, 0xc4, 0x89, 0x87, 0x2e, + 0x02, 0xb7, 0xe5, 0x22, 0x7b, 0x77, 0x55, 0x0a, + 0xf1, 0x33, 0x0e, 0x5a, 0x71, 0xf8, 0xc3, 0x68, + // RemainingBalanceOwner type ID + 0x00, 0x00, 0x00, 0x0b, + // locktime + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // threshold + 0x00, 0x00, 0x00, 0x01, + // number of addresses + 0x00, 0x00, 0x00, 0x01, + // Addrs[0] + 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0x44, 0x55, 0x66, 0x77, // secp256k1fx authorization type ID 0x00, 0x00, 0x00, 0x0a, // number of signatures needed in authorization @@ -462,6 +538,9 @@ func TestConvertSubnetTxSerialization(t *testing.T) { } func TestConvertSubnetTxSyntacticVerify(t *testing.T) { + sk, err := bls.NewSecretKey() + require.NoError(t, err) + var ( ctx = snowtest.Context(t, ids.GenerateTestID()) validBaseTx = BaseTx{ @@ -470,8 +549,17 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { BlockchainID: ctx.ChainID, }, } - validSubnetID = ids.GenerateTestID() - invalidAddress = make(types.JSONByteSlice, MaxSubnetAddressLength+1) + validSubnetID = ids.GenerateTestID() + invalidAddress = make(types.JSONByteSlice, MaxSubnetAddressLength+1) + validValidators = []ConvertSubnetValidator{ + { + NodeID: ids.GenerateTestNodeID(), + Weight: 1, + Balance: 1, + Signer: signer.NewProofOfPossession(sk), + RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + }, + } validSubnetAuth = &secp256k1fx.Input{} invalidSubnetAuth = &secp256k1fx.Input{ SigIndices: []uint32{1, 0}, @@ -498,6 +586,7 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { }, Subnet: constants.PrimaryNetworkID, Address: invalidAddress, + Validators: nil, SubnetAuth: invalidSubnetAuth, }, expectedErr: nil, @@ -507,6 +596,7 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { tx: &ConvertSubnetTx{ BaseTx: validBaseTx, Subnet: constants.PrimaryNetworkID, + Validators: validValidators, SubnetAuth: validSubnetAuth, }, expectedErr: ErrConvertPermissionlessSubnet, @@ -517,15 +607,110 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { BaseTx: validBaseTx, Subnet: validSubnetID, Address: invalidAddress, + Validators: validValidators, SubnetAuth: validSubnetAuth, }, expectedErr: ErrAddressTooLong, }, + { + name: "invalid number of validators", + tx: &ConvertSubnetTx{ + BaseTx: validBaseTx, + Subnet: validSubnetID, + Validators: nil, + SubnetAuth: validSubnetAuth, + }, + expectedErr: ErrConvertMustIncludeValidators, + }, + { + name: "invalid validator order", + tx: &ConvertSubnetTx{ + BaseTx: validBaseTx, + Subnet: validSubnetID, + Validators: []ConvertSubnetValidator{ + { + NodeID: ids.NodeID{1}, + }, + { + NodeID: ids.NodeID{0}, + }, + }, + SubnetAuth: validSubnetAuth, + }, + expectedErr: ErrConvertValidatorsNotSortedAndUnique, + }, + { + name: "invalid validator weight", + tx: &ConvertSubnetTx{ + BaseTx: validBaseTx, + Subnet: validSubnetID, + Validators: []ConvertSubnetValidator{ + { + Weight: 0, + Signer: signer.NewProofOfPossession(sk), + RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + }, + }, + SubnetAuth: validSubnetAuth, + }, + expectedErr: ErrZeroWeight, + }, + { + name: "invalid validator pop", + tx: &ConvertSubnetTx{ + BaseTx: validBaseTx, + Subnet: validSubnetID, + Validators: []ConvertSubnetValidator{ + { + Weight: 1, + Signer: &signer.ProofOfPossession{}, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + }, + }, + SubnetAuth: validSubnetAuth, + }, + expectedErr: bls.ErrFailedPublicKeyDecompress, + }, + { + name: "invalid validator owner", + tx: &ConvertSubnetTx{ + BaseTx: validBaseTx, + Subnet: validSubnetID, + Validators: []ConvertSubnetValidator{ + { + Weight: 1, + Signer: signer.NewProofOfPossession(sk), + RemainingBalanceOwner: &secp256k1fx.OutputOwners{ + Threshold: 1, + }, + }, + }, + SubnetAuth: validSubnetAuth, + }, + expectedErr: secp256k1fx.ErrOutputUnspendable, + }, + { + name: "invalid validator signer", + tx: &ConvertSubnetTx{ + BaseTx: validBaseTx, + Subnet: validSubnetID, + Validators: []ConvertSubnetValidator{ + { + Weight: 1, + Signer: &signer.Empty{}, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + }, + }, + SubnetAuth: validSubnetAuth, + }, + expectedErr: ErrMissingPublicKey, + }, { name: "invalid BaseTx", tx: &ConvertSubnetTx{ BaseTx: BaseTx{}, Subnet: validSubnetID, + Validators: validValidators, SubnetAuth: validSubnetAuth, }, expectedErr: avax.ErrWrongNetworkID, @@ -535,6 +720,7 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { tx: &ConvertSubnetTx{ BaseTx: validBaseTx, Subnet: validSubnetID, + Validators: validValidators, SubnetAuth: invalidSubnetAuth, }, expectedErr: secp256k1fx.ErrInputIndicesNotSortedUnique, @@ -544,6 +730,7 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { tx: &ConvertSubnetTx{ BaseTx: validBaseTx, Subnet: validSubnetID, + Validators: validValidators, SubnetAuth: validSubnetAuth, }, expectedErr: nil, diff --git a/vms/platformvm/txs/convert_subnet_tx_test_complex.json b/vms/platformvm/txs/convert_subnet_tx_test_complex.json index b5178db2f9cb..a0de0c718e9a 100644 --- a/vms/platformvm/txs/convert_subnet_tx_test_complex.json +++ b/vms/platformvm/txs/convert_subnet_tx_test_complex.json @@ -75,6 +75,24 @@ "subnetID": "SkB92YpWm4UpburLz9tEKZw2i67H3FF6YkjaU4BkFUDTG9Xm", "chainID": "NfebWJbJMmUpduqFCF8i1m5pstbVYLP1gGHbacrevXZMhpVMy", "address": "0x000000000000000000000000000000000000dead", + "validators": [ + { + "nodeID": "NodeID-2ZbTY9GatRTrfinAoYiYLcf6CvrPAUYgo", + "weight": 72623859790382856, + "balance": 1000000000, + "signer": { + "publicKey": "0xaff4acb4c5439b5d426cadf9e946d3a452f7de3414d1ad27336133211d8b90cf49fb97eebcdeeef714dc20f54ed0d4d1", + "proofOfPossession": "0x8cfd7909d153b9604b62b143ba36207bb7e64867424480202a67dc68768346d95c90983c2d279c64c43c51136b2a05e01602d52aa6376fda17fa6e2a18a083e49d9c450eab7b89b1d5555da5c489872e02b7e5227b77550af1330e5a71f8c368" + }, + "remainingBalanceOwner": { + "addresses": [ + "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" + ], + "locktime": 0, + "threshold": 1 + } + } + ], "subnetAuthorization": { "signatureIndices": [] } diff --git a/vms/platformvm/txs/convert_subnet_tx_test_simple.json b/vms/platformvm/txs/convert_subnet_tx_test_simple.json index 8463a748141f..5d6848a53f23 100644 --- a/vms/platformvm/txs/convert_subnet_tx_test_simple.json +++ b/vms/platformvm/txs/convert_subnet_tx_test_simple.json @@ -20,6 +20,7 @@ "subnetID": "SkB92YpWm4UpburLz9tEKZw2i67H3FF6YkjaU4BkFUDTG9Xm", "chainID": "NfebWJbJMmUpduqFCF8i1m5pstbVYLP1gGHbacrevXZMhpVMy", "address": "0x000000000000000000000000000000000000dead", + "validators": [], "subnetAuthorization": { "signatureIndices": [ 3 diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 02942e09b76f..5a5ba3b31c6d 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -180,6 +180,7 @@ var ( ids.IDLen + // subnetID ids.IDLen + // chainID wrappers.IntLen + // address length + wrappers.IntLen + // validators length wrappers.IntLen + // subnetAuth typeID wrappers.IntLen, // subnetAuthCredential typeID gas.DBRead: 1, @@ -305,6 +306,46 @@ func inputComplexity(in *avax.TransferableInput) (gas.Dimensions, error) { return complexity, err } +// ConvertSubnetValidatorComplexity returns the complexity the validators add to +// a transaction. +func ConvertSubnetValidatorComplexity(sovs ...txs.ConvertSubnetValidator) (gas.Dimensions, error) { + var complexity gas.Dimensions + for _, sov := range sovs { + sovComplexity, err := convertSubnetValidatorComplexity(sov) + if err != nil { + return gas.Dimensions{}, err + } + + complexity, err = complexity.Add(&sovComplexity) + if err != nil { + return gas.Dimensions{}, err + } + } + return complexity, nil +} + +func convertSubnetValidatorComplexity(sov txs.ConvertSubnetValidator) (gas.Dimensions, error) { + complexity := gas.Dimensions{ + gas.Bandwidth: 20 + 8 + 8 + 4 + 4, + gas.DBRead: 3, + gas.DBWrite: 3, + gas.Compute: 0, // TODO: Add compute complexity + } + + signerComplexity, err := SignerComplexity(sov.Signer) + if err != nil { + return gas.Dimensions{}, err + } + ownerComplexity, err := OwnerComplexity(sov.RemainingBalanceOwner) + if err != nil { + return gas.Dimensions{}, err + } + return complexity.Add( + &signerComplexity, + &ownerComplexity, + ) +} + // OwnerComplexity returns the complexity an owner adds to a transaction. // It does not include the typeID of the owner. func OwnerComplexity(ownerIntf fx.Owner) (gas.Dimensions, error) { @@ -610,12 +651,17 @@ func (c *complexityVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { if err != nil { return err } + validatorComplexity, err := ConvertSubnetValidatorComplexity(tx.Validators...) + if err != nil { + return err + } authComplexity, err := AuthComplexity(tx.SubnetAuth) if err != nil { return err } c.output, err = IntrinsicConvertSubnetTxComplexities.Add( &baseTxComplexity, + &validatorComplexity, &authComplexity, &gas.Dimensions{ gas.Bandwidth: uint64(len(tx.Address)), diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index 5a0b063280fd..5c9c7d3d08d1 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -155,10 +155,12 @@ type Builder interface { // - [subnetID] specifies the subnet to be converted // - [chainID] specifies which chain the manager is deployed on // - [address] specifies the address of the manager + // - [validators] specifies the initial SoVs of the converted subnet NewConvertSubnetTx( subnetID ids.ID, chainID ids.ID, address []byte, + validators []txs.ConvertSubnetValidator, options ...common.Option, ) (*txs.ConvertSubnetTx, error) @@ -782,9 +784,20 @@ func (b *builder) NewConvertSubnetTx( subnetID ids.ID, chainID ids.ID, address []byte, + validators []txs.ConvertSubnetValidator, options ...common.Option, ) (*txs.ConvertSubnetTx, error) { - toBurn := map[ids.ID]uint64{} + var ( + toBurn = map[ids.ID]uint64{} + err error + avaxAssetID = b.context.AVAXAssetID + ) + for _, vdr := range validators { + toBurn[avaxAssetID], err = math.Add(toBurn[avaxAssetID], vdr.Balance) + if err != nil { + return nil, err + } + } toStake := map[ids.ID]uint64{} ops := common.NewOptions(options) @@ -801,12 +814,17 @@ func (b *builder) NewConvertSubnetTx( bytesComplexity := gas.Dimensions{ gas.Bandwidth: additionalBytes, } + validatorComplexity, err := fee.ConvertSubnetValidatorComplexity(validators...) + if err != nil { + return nil, err + } authComplexity, err := fee.AuthComplexity(subnetAuth) if err != nil { return nil, err } complexity, err := fee.IntrinsicConvertSubnetTxComplexities.Add( &bytesComplexity, + &validatorComplexity, &authComplexity, ) if err != nil { @@ -825,6 +843,7 @@ func (b *builder) NewConvertSubnetTx( return nil, err } + utils.Sort(validators) tx := &txs.ConvertSubnetTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.context.NetworkID, @@ -836,6 +855,7 @@ func (b *builder) NewConvertSubnetTx( Subnet: subnetID, ChainID: chainID, Address: address, + Validators: validators, SubnetAuth: subnetAuth, } return tx, b.initCtx(tx) diff --git a/wallet/chain/p/builder/builder_with_options.go b/wallet/chain/p/builder/builder_with_options.go index 4a473cf50bac..6201dde1fa8f 100644 --- a/wallet/chain/p/builder/builder_with_options.go +++ b/wallet/chain/p/builder/builder_with_options.go @@ -159,12 +159,14 @@ func (b *builderWithOptions) NewConvertSubnetTx( subnetID ids.ID, chainID ids.ID, address []byte, + validators []txs.ConvertSubnetValidator, options ...common.Option, ) (*txs.ConvertSubnetTx, error) { return b.builder.NewConvertSubnetTx( subnetID, chainID, address, + validators, common.UnionOptions(b.options, options)..., ) } diff --git a/wallet/chain/p/builder_test.go b/wallet/chain/p/builder_test.go index 54892dfb32c7..a42725aec4f7 100644 --- a/wallet/chain/p/builder_test.go +++ b/wallet/chain/p/builder_test.go @@ -4,6 +4,7 @@ package p import ( + "math/rand" "testing" "time" @@ -667,9 +668,35 @@ func TestAddPermissionlessDelegatorTx(t *testing.T) { } func TestConvertSubnetTx(t *testing.T) { + sk0, err := bls.NewSecretKey() + require.NoError(t, err) + sk1, err := bls.NewSecretKey() + require.NoError(t, err) + var ( - chainID = ids.GenerateTestID() - address = utils.RandomBytes(32) + chainID = ids.GenerateTestID() + address = utils.RandomBytes(32) + validators = []txs.ConvertSubnetValidator{ + { + NodeID: ids.GenerateTestNodeID(), + Weight: rand.Uint64(), //#nosec G404 + Balance: units.Avax, + Signer: signer.NewProofOfPossession(sk0), + RemainingBalanceOwner: &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + }, + { + NodeID: ids.GenerateTestNodeID(), + Weight: rand.Uint64(), //#nosec G404 + Balance: 2 * units.Avax, + Signer: signer.NewProofOfPossession(sk1), + RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + }, + } ) for _, e := range testEnvironmentPostEtna { t.Run(e.name, func(t *testing.T) { @@ -686,6 +713,7 @@ func TestConvertSubnetTx(t *testing.T) { subnetID, chainID, address, + validators, common.WithMemo(e.memo), ) require.NoError(err) @@ -693,6 +721,8 @@ func TestConvertSubnetTx(t *testing.T) { require.Equal(chainID, utx.ChainID) require.Equal(types.JSONByteSlice(address), utx.Address) require.Equal(types.JSONByteSlice(e.memo), utx.Memo) + require.True(utils.IsSortedAndUnique(utx.Validators)) + require.Equal(validators, utx.Validators) requireFeeIsCorrect( require, e.feeCalculator, @@ -700,7 +730,9 @@ func TestConvertSubnetTx(t *testing.T) { &utx.BaseTx.BaseTx, nil, nil, - nil, + map[ids.ID]uint64{ + e.context.AVAXAssetID: 3 * units.Avax, + }, ) }) } diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index 4e2886d41e88..d7137e91b6d7 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -145,6 +145,7 @@ type Wallet interface { subnetID ids.ID, chainID ids.ID, address []byte, + validators []txs.ConvertSubnetValidator, options ...common.Option, ) (*txs.Tx, error) @@ -392,9 +393,10 @@ func (w *wallet) IssueConvertSubnetTx( subnetID ids.ID, chainID ids.ID, address []byte, + validators []txs.ConvertSubnetValidator, options ...common.Option, ) (*txs.Tx, error) { - utx, err := w.builder.NewConvertSubnetTx(subnetID, chainID, address, options...) + utx, err := w.builder.NewConvertSubnetTx(subnetID, chainID, address, validators, options...) if err != nil { return nil, err } diff --git a/wallet/chain/p/wallet/with_options.go b/wallet/chain/p/wallet/with_options.go index 7082764aa18d..3027b46d5428 100644 --- a/wallet/chain/p/wallet/with_options.go +++ b/wallet/chain/p/wallet/with_options.go @@ -147,12 +147,14 @@ func (w *withOptions) IssueConvertSubnetTx( subnetID ids.ID, chainID ids.ID, address []byte, + validators []txs.ConvertSubnetValidator, options ...common.Option, ) (*txs.Tx, error) { return w.wallet.IssueConvertSubnetTx( subnetID, chainID, address, + validators, common.UnionOptions(w.options, options)..., ) } From d8bdb24671b85f572207da0fca1b7f6008774767 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 18 Sep 2024 11:36:36 -0400 Subject: [PATCH 052/199] comment --- wallet/chain/p/wallet/wallet.go | 1 + 1 file changed, 1 insertion(+) diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index d7137e91b6d7..2f1d788fd7d5 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -141,6 +141,7 @@ type Wallet interface { // - [subnetID] specifies the subnet to be converted // - [chainID] specifies which chain the manager is deployed on // - [address] specifies the address of the manager + // - [validators] specifies the initial SoVs of the converted subnet IssueConvertSubnetTx( subnetID ids.ID, chainID ids.ID, From 03d56aadaff4e7a7d4f5c84b72f1e2d9c7df46db Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 18 Sep 2024 11:46:35 -0400 Subject: [PATCH 053/199] cleanup complexity --- vms/platformvm/txs/fee/complexity.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 5a5ba3b31c6d..2642b51e7b63 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -62,13 +62,21 @@ const ( intrinsicSECP256k1FxSignatureBandwidth = wrappers.IntLen + // signature index secp256k1.SignatureLen // signature length + intrinsicConvertSubnetValidatorBandwidth = ids.NodeIDLen + // nodeID + wrappers.LongLen + // weight + wrappers.LongLen + // balance + wrappers.IntLen + // signer typeID + wrappers.IntLen // owner typeID + intrinsicPoPBandwidth = bls.PublicKeyLen + // public key bls.SignatureLen // signature - intrinsicInputDBRead = 1 + intrinsicInputDBRead = 1 + intrinsicConvertSubnetValidatorDBRead = 3 // TODO: Update - intrinsicInputDBWrite = 1 - intrinsicOutputDBWrite = 1 + intrinsicInputDBWrite = 1 + intrinsicOutputDBWrite = 1 + intrinsicConvertSubnetValidatorDBWrite = 3 // TODO: Update ) var ( @@ -326,9 +334,9 @@ func ConvertSubnetValidatorComplexity(sovs ...txs.ConvertSubnetValidator) (gas.D func convertSubnetValidatorComplexity(sov txs.ConvertSubnetValidator) (gas.Dimensions, error) { complexity := gas.Dimensions{ - gas.Bandwidth: 20 + 8 + 8 + 4 + 4, - gas.DBRead: 3, - gas.DBWrite: 3, + gas.Bandwidth: intrinsicConvertSubnetValidatorBandwidth, + gas.DBRead: intrinsicConvertSubnetValidatorDBRead, + gas.DBWrite: intrinsicConvertSubnetValidatorDBWrite, gas.Compute: 0, // TODO: Add compute complexity } From a46ced05d326316e89f9a46f0713c3e080cec862 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 18 Sep 2024 12:32:42 -0400 Subject: [PATCH 054/199] test complexity --- vms/platformvm/txs/convert_subnet_tx_test.go | 2 + vms/platformvm/txs/fee/calculator_test.go | 10 +-- vms/platformvm/txs/fee/complexity_test.go | 90 ++++++++++++++++++++ 3 files changed, 97 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/txs/convert_subnet_tx_test.go b/vms/platformvm/txs/convert_subnet_tx_test.go index 3d122d5c9cf4..1df33946d886 100644 --- a/vms/platformvm/txs/convert_subnet_tx_test.go +++ b/vms/platformvm/txs/convert_subnet_tx_test.go @@ -533,6 +533,8 @@ func TestConvertSubnetTxSerialization(t *testing.T) { strings.ReplaceAll(string(test.expectedJSON), "\r\n", "\n"), string(txJSON), ) + + t.Fatalf("%x", test.expectedBytes) }) } } diff --git a/vms/platformvm/txs/fee/calculator_test.go b/vms/platformvm/txs/fee/calculator_test.go index 172f927e9796..25516621240b 100644 --- a/vms/platformvm/txs/fee/calculator_test.go +++ b/vms/platformvm/txs/fee/calculator_test.go @@ -221,15 +221,15 @@ var ( }, { name: "ConvertSubnetTx", - tx: "00000000002300015b380000000000000000000000000000000000000000000000000000000000000000000000012a16b813b6a4a64d8e9b3f11460b782fcc319364bc038915af56834b72043ce80000000700470de4d97cdcc00000000000000000000000010000000180fa21568b6a2ef338a773ba18bfc0cb493af926000000018d65db2676f4733a7d263ad14606ddbc2f1996bb2998358f4b6f1e01297d1da5000000002a16b813b6a4a64d8e9b3f11460b782fcc319364bc038915af56834b72043ce80000000500470de4d98c1f000000000100000000000000008d65db2676f4733a7d263ad14606ddbc2f1996bb2998358f4b6f1e01297d1da55fa29ed4356903dac2364713c60f57d8472c7dda4a5e08d88a88ad8ea71aed6000000007616464726573730000000a0000000100000000000000020000000900000001c990ecf3f39646c4c90cb1f5cc2a9a98c33df1a9a41a084e7f3e7b2afe10fd853068a20ad4ddf83c087b6311ab0fdab339ca529f57cda3329ca31b142987c223000000000900000001c990ecf3f39646c4c90cb1f5cc2a9a98c33df1a9a41a084e7f3e7b2afe10fd853068a20ad4ddf83c087b6311ab0fdab339ca529f57cda3329ca31b142987c22300", + tx: "00000000002300003039000000000000000000000000000000000000000000000000000000000000000000000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db0000000700470de4a3e7309a000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c00000001000000000000000000000000000000000000000000000000000000000000000000000000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db0000000500470de4df820000000000010000000000000000a0673b4ee5ec44e57c8ab250dd7cd7b68d04421f64bd6559a4284a3ee358ff2b00000000000000000000000000000000000000000000000000000000000000000000000000000001e902a9a86640bfdb1cd0e36c0cc982b83e5765fa0000000000000001000000003b9aca000000001c8ef822e59dbe6330ab626cc459d302ada16ed1dc0a2abf7b98e8f297fdad93adc13467d9cd4c152b3448c1e036172142b20b33fd46c37cfaeacc45e87d88ab732e70cc8d123c7a21134a5b0846d888c53fec7ba6f56f82dad2cb04593be5d1610dc45220b09fa3932893081df15cbd1ce1d5410c48d3f6db135d6075007dc41dc88aefce6573353d1917c95b7cce46ac0000000b000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c0000000a0000000100000000000000020000000900000001376cfb1cb202f6c46ae19c7e05153ccd1fd37e8028ee55ff9a0d08deace609e42bf09f129e1049124446520fb2586fb8dce40de31cfc2c83947337210fe6b4be000000000900000001376cfb1cb202f6c46ae19c7e05153ccd1fd37e8028ee55ff9a0d08deace609e42bf09f129e1049124446520fb2586fb8dce40de31cfc2c83947337210fe6b4be00", expectedStaticFeeErr: ErrUnsupportedTx, expectedComplexity: gas.Dimensions{ - gas.Bandwidth: 459, // The length of the tx in bytes - gas.DBRead: IntrinsicConvertSubnetTxComplexities[gas.DBRead] + intrinsicInputDBRead, - gas.DBWrite: IntrinsicConvertSubnetTxComplexities[gas.DBWrite] + intrinsicInputDBWrite + intrinsicOutputDBWrite, + gas.Bandwidth: 680, // The length of the tx in bytes + gas.DBRead: IntrinsicConvertSubnetTxComplexities[gas.DBRead] + intrinsicInputDBRead + intrinsicConvertSubnetValidatorDBRead, + gas.DBWrite: IntrinsicConvertSubnetTxComplexities[gas.DBWrite] + intrinsicInputDBWrite + intrinsicOutputDBWrite + intrinsicConvertSubnetValidatorDBWrite, gas.Compute: 0, // TODO: implement }, - expectedDynamicFee: 175_900, + expectedDynamicFee: 348_000, }, } ) diff --git a/vms/platformvm/txs/fee/complexity_test.go b/vms/platformvm/txs/fee/complexity_test.go index 0dd9ba90b3f0..3ad0dfb29d31 100644 --- a/vms/platformvm/txs/fee/complexity_test.go +++ b/vms/platformvm/txs/fee/complexity_test.go @@ -367,6 +367,96 @@ func TestInputComplexity(t *testing.T) { } } +func TestConvertSubnetValidatorComplexity(t *testing.T) { + tests := []struct { + name string + vdr txs.ConvertSubnetValidator + expected gas.Dimensions + expectedErr error + }{ + { + name: "any can spend", + vdr: txs.ConvertSubnetValidator{ + Signer: &signer.ProofOfPossession{}, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + }, + expected: gas.Dimensions{ + gas.Bandwidth: 204, + gas.DBRead: 3, + gas.DBWrite: 3, + gas.Compute: 0, // TODO: implement + }, + expectedErr: nil, + }, + { + name: "single owner", + vdr: txs.ConvertSubnetValidator{ + Signer: &signer.ProofOfPossession{}, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + }, + expected: gas.Dimensions{ + gas.Bandwidth: 224, + gas.DBRead: 3, + gas.DBWrite: 3, + gas.Compute: 0, // TODO: implement + }, + expectedErr: nil, + }, + { + name: "invalid signer", + vdr: txs.ConvertSubnetValidator{ + Signer: nil, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + }, + expected: gas.Dimensions{ + gas.Bandwidth: 0, + gas.DBRead: 0, + gas.DBWrite: 0, + gas.Compute: 0, + }, + expectedErr: errUnsupportedSigner, + }, + { + name: "invalid owner", + vdr: txs.ConvertSubnetValidator{ + Signer: &signer.ProofOfPossession{}, + RemainingBalanceOwner: nil, + }, + expected: gas.Dimensions{ + gas.Bandwidth: 0, + gas.DBRead: 0, + gas.DBWrite: 0, + gas.Compute: 0, + }, + expectedErr: errUnsupportedOwner, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + actual, err := ConvertSubnetValidatorComplexity(test.vdr) + require.ErrorIs(err, test.expectedErr) + require.Equal(test.expected, actual) + + if err != nil { + return + } + + vdrBytes, err := txs.Codec.Marshal(txs.CodecVersion, test.vdr) + require.NoError(err) + + numBytesWithoutCodecVersion := uint64(len(vdrBytes) - codec.VersionSize) + require.Equal(numBytesWithoutCodecVersion, actual[gas.Bandwidth]) + }) + } +} + func TestOwnerComplexity(t *testing.T) { tests := []struct { name string From 6f64e69b303ac873f9a8bd04ff87af2eba15452b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 18 Sep 2024 12:33:59 -0400 Subject: [PATCH 055/199] nit --- vms/platformvm/txs/fee/complexity_test.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/vms/platformvm/txs/fee/complexity_test.go b/vms/platformvm/txs/fee/complexity_test.go index 3ad0dfb29d31..9e49b206d86a 100644 --- a/vms/platformvm/txs/fee/complexity_test.go +++ b/vms/platformvm/txs/fee/complexity_test.go @@ -413,12 +413,7 @@ func TestConvertSubnetValidatorComplexity(t *testing.T) { Signer: nil, RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, }, - expected: gas.Dimensions{ - gas.Bandwidth: 0, - gas.DBRead: 0, - gas.DBWrite: 0, - gas.Compute: 0, - }, + expected: gas.Dimensions{}, expectedErr: errUnsupportedSigner, }, { @@ -427,12 +422,7 @@ func TestConvertSubnetValidatorComplexity(t *testing.T) { Signer: &signer.ProofOfPossession{}, RemainingBalanceOwner: nil, }, - expected: gas.Dimensions{ - gas.Bandwidth: 0, - gas.DBRead: 0, - gas.DBWrite: 0, - gas.Compute: 0, - }, + expected: gas.Dimensions{}, expectedErr: errUnsupportedOwner, }, } From ba45ab73f7e81df9d98ca9d5fcbd9c90d50d153a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 18 Sep 2024 12:42:53 -0400 Subject: [PATCH 056/199] Add todo --- vms/platformvm/txs/executor/standard_tx_executor.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index e2669c3510e4..f924ec6fee9c 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -540,7 +540,7 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { } sov := state.SubnetOnlyValidator{ - ValidationID: txID.Prefix(uint64(i)), + ValidationID: txID.Prefix(uint64(i)), // TODO: The spec says this should be a postfix, not a preifx SubnetID: tx.Subnet, NodeID: vdr.NodeID, PublicKey: bls.PublicKeyToUncompressedBytes(vdr.Signer.Key()), @@ -551,6 +551,7 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { EndAccumulatedFee: 0, // If Balance is 0, this is 0 } if vdr.Balance != 0 { + // We are attempting to add an active validator if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeCapacity { return errMaxNumActiveValidators } From b2d76fe2bca4823f9b5ce23d6442052ef2b00165 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 18 Sep 2024 12:47:10 -0400 Subject: [PATCH 057/199] Remove debug print --- vms/platformvm/txs/convert_subnet_tx_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/vms/platformvm/txs/convert_subnet_tx_test.go b/vms/platformvm/txs/convert_subnet_tx_test.go index 1df33946d886..3d122d5c9cf4 100644 --- a/vms/platformvm/txs/convert_subnet_tx_test.go +++ b/vms/platformvm/txs/convert_subnet_tx_test.go @@ -533,8 +533,6 @@ func TestConvertSubnetTxSerialization(t *testing.T) { strings.ReplaceAll(string(test.expectedJSON), "\r\n", "\n"), string(txJSON), ) - - t.Fatalf("%x", test.expectedBytes) }) } } From 9e6b6b11ef868da7d3ce40c0989621fb7695b69b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 18 Sep 2024 12:55:53 -0400 Subject: [PATCH 058/199] nit --- vms/platformvm/txs/convert_subnet_tx.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/vms/platformvm/txs/convert_subnet_tx.go b/vms/platformvm/txs/convert_subnet_tx.go index 34ed7996ccc6..46a363f5bc16 100644 --- a/vms/platformvm/txs/convert_subnet_tx.go +++ b/vms/platformvm/txs/convert_subnet_tx.go @@ -66,15 +66,9 @@ func (tx *ConvertSubnetTx) SyntacticVerify(ctx *snow.Context) error { return err } for _, vdr := range tx.Validators { - if vdr.Weight == 0 { - return ErrZeroWeight - } - if err := verify.All(vdr.Signer, vdr.RemainingBalanceOwner); err != nil { + if err := vdr.Verify(); err != nil { return err } - if vdr.Signer.Key() == nil { - return ErrMissingPublicKey - } } if err := tx.SubnetAuth.Verify(); err != nil { return err @@ -108,3 +102,16 @@ type ConvertSubnetValidator struct { func (v ConvertSubnetValidator) Compare(o ConvertSubnetValidator) int { return v.NodeID.Compare(o.NodeID) } + +func (v *ConvertSubnetValidator) Verify() error { + if v.Weight == 0 { + return ErrZeroWeight + } + if err := verify.All(v.Signer, v.RemainingBalanceOwner); err != nil { + return err + } + if v.Signer.Key() == nil { + return ErrMissingPublicKey + } + return nil +} From ca33847067eb8ff4fdda5016bd1e2c7f0e94c275 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 18 Sep 2024 13:26:25 -0400 Subject: [PATCH 059/199] Update read/write complexity --- vms/platformvm/txs/fee/calculator_test.go | 4 ++-- vms/platformvm/txs/fee/complexity.go | 11 +++++------ vms/platformvm/txs/fee/complexity_test.go | 8 ++++---- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/vms/platformvm/txs/fee/calculator_test.go b/vms/platformvm/txs/fee/calculator_test.go index 25516621240b..d1674b77f7ed 100644 --- a/vms/platformvm/txs/fee/calculator_test.go +++ b/vms/platformvm/txs/fee/calculator_test.go @@ -225,11 +225,11 @@ var ( expectedStaticFeeErr: ErrUnsupportedTx, expectedComplexity: gas.Dimensions{ gas.Bandwidth: 680, // The length of the tx in bytes - gas.DBRead: IntrinsicConvertSubnetTxComplexities[gas.DBRead] + intrinsicInputDBRead + intrinsicConvertSubnetValidatorDBRead, + gas.DBRead: IntrinsicConvertSubnetTxComplexities[gas.DBRead] + intrinsicInputDBRead, gas.DBWrite: IntrinsicConvertSubnetTxComplexities[gas.DBWrite] + intrinsicInputDBWrite + intrinsicOutputDBWrite + intrinsicConvertSubnetValidatorDBWrite, gas.Compute: 0, // TODO: implement }, - expectedDynamicFee: 348_000, + expectedDynamicFee: 368_000, }, } ) diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 2642b51e7b63..50c5f377e34f 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -71,12 +71,11 @@ const ( intrinsicPoPBandwidth = bls.PublicKeyLen + // public key bls.SignatureLen // signature - intrinsicInputDBRead = 1 - intrinsicConvertSubnetValidatorDBRead = 3 // TODO: Update + intrinsicInputDBRead = 1 intrinsicInputDBWrite = 1 intrinsicOutputDBWrite = 1 - intrinsicConvertSubnetValidatorDBWrite = 3 // TODO: Update + intrinsicConvertSubnetValidatorDBWrite = 4 // weight diff + pub key diff + subnetID/nodeID + validationID ) var ( @@ -191,8 +190,8 @@ var ( wrappers.IntLen + // validators length wrappers.IntLen + // subnetAuth typeID wrappers.IntLen, // subnetAuthCredential typeID - gas.DBRead: 1, - gas.DBWrite: 1, + gas.DBRead: 2, // subnet auth + manager lookup + gas.DBWrite: 2, // manager + weight gas.Compute: 0, } @@ -335,7 +334,7 @@ func ConvertSubnetValidatorComplexity(sovs ...txs.ConvertSubnetValidator) (gas.D func convertSubnetValidatorComplexity(sov txs.ConvertSubnetValidator) (gas.Dimensions, error) { complexity := gas.Dimensions{ gas.Bandwidth: intrinsicConvertSubnetValidatorBandwidth, - gas.DBRead: intrinsicConvertSubnetValidatorDBRead, + gas.DBRead: 0, gas.DBWrite: intrinsicConvertSubnetValidatorDBWrite, gas.Compute: 0, // TODO: Add compute complexity } diff --git a/vms/platformvm/txs/fee/complexity_test.go b/vms/platformvm/txs/fee/complexity_test.go index 9e49b206d86a..bf51693987ca 100644 --- a/vms/platformvm/txs/fee/complexity_test.go +++ b/vms/platformvm/txs/fee/complexity_test.go @@ -382,8 +382,8 @@ func TestConvertSubnetValidatorComplexity(t *testing.T) { }, expected: gas.Dimensions{ gas.Bandwidth: 204, - gas.DBRead: 3, - gas.DBWrite: 3, + gas.DBRead: 0, + gas.DBWrite: 4, gas.Compute: 0, // TODO: implement }, expectedErr: nil, @@ -401,8 +401,8 @@ func TestConvertSubnetValidatorComplexity(t *testing.T) { }, expected: gas.Dimensions{ gas.Bandwidth: 224, - gas.DBRead: 3, - gas.DBWrite: 3, + gas.DBRead: 0, + gas.DBWrite: 4, gas.Compute: 0, // TODO: implement }, expectedErr: nil, From 53e621b5ae0e1f17065d6b709ed8ada0164c94cc Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 18 Sep 2024 17:50:41 -0400 Subject: [PATCH 060/199] Update execution tests --- .../txs/executor/standard_tx_executor_test.go | 73 ++++++++++++++++--- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index e11ad73ac5d9..47286a461e5e 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -2378,8 +2378,10 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { var ( ctx = snowtest.Context(t, constants.PlatformChainID) defaultConfig = &config.Config{ - DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, - UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), + DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, + ValidatorFeeCapacity: genesis.LocalParams.ValidatorFeeCapacity, + ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, + UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), } baseState = statetest.New(t, statetest.Config{ Upgrades: defaultConfig.UpgradeConfig, @@ -2424,26 +2426,31 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { require.NoError(t, diff.Apply(baseState)) require.NoError(t, baseState.Commit()) - subnetID := createSubnetTx.ID() + var ( + subnetID = createSubnetTx.ID() + nodeID = ids.GenerateTestNodeID() + ) tests := []struct { name string builderOptions []common.Option - updateExecutor func(executor *StandardTxExecutor) + updateExecutor func(executor *StandardTxExecutor) error expectedErr error }{ { name: "invalid prior to E-Upgrade", - updateExecutor: func(e *StandardTxExecutor) { + updateExecutor: func(e *StandardTxExecutor) error { e.Backend.Config = &config.Config{ UpgradeConfig: upgradetest.GetConfig(upgradetest.Durango), } + return nil }, expectedErr: errEtnaUpgradeNotActive, }, { name: "tx fails syntactic verification", - updateExecutor: func(e *StandardTxExecutor) { + updateExecutor: func(e *StandardTxExecutor) error { e.Backend.Ctx = snowtest.Context(t, ids.GenerateTestID()) + return nil }, expectedErr: avax.ErrWrongChainID, }, @@ -2456,46 +2463,76 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { }, { name: "fail subnet authorization", - updateExecutor: func(e *StandardTxExecutor) { + updateExecutor: func(e *StandardTxExecutor) error { e.State.SetSubnetOwner(subnetID, &secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{ ids.GenerateTestShortID(), }, }) + return nil }, expectedErr: errUnauthorizedSubnetModification, }, { name: "invalid if subnet is transformed", - updateExecutor: func(e *StandardTxExecutor) { + updateExecutor: func(e *StandardTxExecutor) error { e.State.AddSubnetTransformation(&txs.Tx{Unsigned: &txs.TransformSubnetTx{ Subnet: subnetID, }}) + return nil }, expectedErr: errIsImmutable, }, { name: "invalid if subnet is converted", - updateExecutor: func(e *StandardTxExecutor) { + updateExecutor: func(e *StandardTxExecutor) error { e.State.SetSubnetManager(subnetID, ids.GenerateTestID(), nil) + return nil }, expectedErr: errIsImmutable, }, { name: "invalid fee calculation", - updateExecutor: func(e *StandardTxExecutor) { + updateExecutor: func(e *StandardTxExecutor) error { e.FeeCalculator = fee.NewStaticCalculator(e.Config.StaticFeeConfig) + return nil }, expectedErr: fee.ErrUnsupportedTx, }, + { + name: "too many active validators", + updateExecutor: func(e *StandardTxExecutor) error { + e.Backend.Config = &config.Config{ + DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, + ValidatorFeeCapacity: 0, + ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, + UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), + } + return nil + }, + expectedErr: errMaxNumActiveValidators, + }, + { + name: "invalid subnet only validator", + updateExecutor: func(e *StandardTxExecutor) error { + return e.State.PutSubnetOnlyValidator(state.SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: subnetID, + NodeID: nodeID, + Weight: 1, + }) + }, + expectedErr: state.ErrDuplicateSubnetOnlyValidator, + }, { name: "insufficient fee", - updateExecutor: func(e *StandardTxExecutor) { + updateExecutor: func(e *StandardTxExecutor) error { e.FeeCalculator = fee.NewDynamicCalculator( e.Config.DynamicFeeConfig.Weights, 100*genesis.LocalParams.DynamicFeeConfig.MinPrice, ) + return nil }, expectedErr: utxo.ErrInsufficientUnlockedFunds, }, @@ -2507,6 +2544,9 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { t.Run(test.name, func(t *testing.T) { require := require.New(t) + sk, err := bls.NewSecretKey() + require.NoError(err) + // Create the ConvertSubnetTx var ( wallet = txstest.NewWallet( @@ -2525,6 +2565,15 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { subnetID, chainID, address, + []txs.ConvertSubnetValidator{ + { + NodeID: nodeID, + Weight: 1, + Balance: 1, + Signer: signer.NewProofOfPossession(sk), + RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + }, + }, test.builderOptions..., ) require.NoError(err) @@ -2545,7 +2594,7 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { State: diff, } if test.updateExecutor != nil { - test.updateExecutor(executor) + require.NoError(test.updateExecutor(executor)) } err = convertSubnetTx.Unsigned.Visit(executor) From 449994aa2f3f257e8abb596b6c9f798fa5a62788 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 22 Sep 2024 23:29:23 -0400 Subject: [PATCH 061/199] write subnet public key diffs --- vms/platformvm/state/state.go | 334 ++++---- vms/platformvm/state/state_test.go | 1108 ++++++++++---------------- vms/platformvm/validators/manager.go | 92 +-- 3 files changed, 649 insertions(+), 885 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 20a5d42bed03..12405f4ab60e 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1898,19 +1898,28 @@ func (s *state) initValidatorSets() error { } // Load primary network and non-ACP77 validators - for subnetID, validators := range s.currentStakers.validators { + primaryNetworkValidators := s.currentStakers.validators[constants.PrimaryNetworkID] + for subnetID, subnetValidators := range s.currentStakers.validators { if s.validators.Count(subnetID) != 0 { // Enforce the invariant that the validator set is empty here. return fmt.Errorf("%w: %s", errValidatorSetAlreadyPopulated, subnetID) } - for nodeID, validator := range validators { - validatorStaker := validator.validator - if err := s.validators.AddStaker(subnetID, nodeID, validatorStaker.PublicKey, validatorStaker.TxID, validatorStaker.Weight); err != nil { + for nodeID, subnetValidator := range subnetValidators { + primaryValidator, ok := primaryNetworkValidators[nodeID] + if !ok { + return errors.New("subnet validator without corresponding primary network validator") + } + + var ( + primaryStaker = primaryValidator.validator + subnetStaker = subnetValidator.validator + ) + if err := s.validators.AddStaker(subnetID, nodeID, primaryStaker.PublicKey, subnetStaker.TxID, subnetStaker.Weight); err != nil { return err } - delegatorIterator := iterator.FromTree(validator.delegators) + delegatorIterator := iterator.FromTree(subnetValidator.delegators) for delegatorIterator.Next() { delegatorStaker := delegatorIterator.Value() if err := s.validators.AddWeight(subnetID, nodeID, delegatorStaker.Weight); err != nil { @@ -2491,164 +2500,221 @@ func (s *state) makeSubnetOnlyValidatorHistoricalDiffs() (map[subnetIDNodeID]*va func (s *state) writeCurrentStakers(updateValidators bool, height uint64, codecVersion uint16) error { for subnetID, validatorDiffs := range s.currentStakers.validatorDiffs { + // Write the primary network diff last + if subnetID == constants.PrimaryNetworkID { + continue + } + delete(s.currentStakers.validatorDiffs, subnetID) - // Select db to write to - validatorDB := s.currentSubnetValidatorList - delegatorDB := s.currentSubnetDelegatorList - if subnetID == constants.PrimaryNetworkID { - validatorDB = s.currentValidatorList - delegatorDB = s.currentDelegatorList + err := s.writeCurrentStakersSubnetDiff( + subnetID, + validatorDiffs, + updateValidators, + height, + codecVersion, + ) + if err != nil { + return err } + } - // Record the change in weight and/or public key for each validator. - for nodeID, validatorDiff := range validatorDiffs { - // Copy [nodeID] so it doesn't get overwritten next iteration. - nodeID := nodeID + if validatorDiffs, ok := s.currentStakers.validatorDiffs[constants.PrimaryNetworkID]; ok { + delete(s.currentStakers.validatorDiffs, constants.PrimaryNetworkID) - weightDiff := &ValidatorWeightDiff{ - Decrease: validatorDiff.validatorStatus == deleted, - } - switch validatorDiff.validatorStatus { - case added: - staker := validatorDiff.validator - weightDiff.Amount = staker.Weight - - // Invariant: Only the Primary Network contains non-nil public - // keys. - if staker.PublicKey != nil { - // Record that the public key for the validator is being - // added. This means the prior value for the public key was - // nil. - err := s.validatorPublicKeyDiffsDB.Put( - marshalDiffKey(constants.PrimaryNetworkID, height, nodeID), - nil, - ) - if err != nil { - return err - } - } + err := s.writeCurrentStakersSubnetDiff( + constants.PrimaryNetworkID, + validatorDiffs, + updateValidators, + height, + codecVersion, + ) + if err != nil { + return err + } + } - // The validator is being added. - // - // Invariant: It's impossible for a delegator to have been - // rewarded in the same block that the validator was added. - startTime := uint64(staker.StartTime.Unix()) - metadata := &validatorMetadata{ - txID: staker.TxID, - lastUpdated: staker.StartTime, - - UpDuration: 0, - LastUpdated: startTime, - StakerStartTime: startTime, - PotentialReward: staker.PotentialReward, - PotentialDelegateeReward: 0, - } + // TODO: Move validator set management out of the state package + // + // Attempt to update the stake metrics + if !updateValidators { + return nil + } - metadataBytes, err := MetadataCodec.Marshal(codecVersion, metadata) - if err != nil { - return fmt.Errorf("failed to serialize current validator: %w", err) - } + totalWeight, err := s.validators.TotalWeight(constants.PrimaryNetworkID) + if err != nil { + return fmt.Errorf("failed to get total weight of primary network: %w", err) + } - if err = validatorDB.Put(staker.TxID[:], metadataBytes); err != nil { - return fmt.Errorf("failed to write current validator to list: %w", err) - } + s.metrics.SetLocalStake(s.validators.GetWeight(constants.PrimaryNetworkID, s.ctx.NodeID)) + s.metrics.SetTotalStake(totalWeight) + return nil +} - s.validatorState.LoadValidatorMetadata(nodeID, subnetID, metadata) - case deleted: - staker := validatorDiff.validator - weightDiff.Amount = staker.Weight - - // Invariant: Only the Primary Network contains non-nil public - // keys. - if staker.PublicKey != nil { - // Record that the public key for the validator is being - // removed. This means we must record the prior value of the - // public key. - // - // Note: We store the uncompressed public key here as it is - // significantly more efficient to parse when applying - // diffs. - err := s.validatorPublicKeyDiffsDB.Put( - marshalDiffKey(constants.PrimaryNetworkID, height, nodeID), - bls.PublicKeyToUncompressedBytes(staker.PublicKey), - ) - if err != nil { - return err - } - } +func (s *state) writeCurrentStakersSubnetDiff( + subnetID ids.ID, + validatorDiffs map[ids.NodeID]*diffValidator, + updateValidators bool, + height uint64, + codecVersion uint16, +) error { + // Select db to write to + validatorDB := s.currentSubnetValidatorList + delegatorDB := s.currentSubnetDelegatorList + if subnetID == constants.PrimaryNetworkID { + validatorDB = s.currentValidatorList + delegatorDB = s.currentDelegatorList + } - if err := validatorDB.Delete(staker.TxID[:]); err != nil { - return fmt.Errorf("failed to delete current staker: %w", err) - } + // Record the change in weight and/or public key for each validator. + for nodeID, validatorDiff := range validatorDiffs { + // Copy [nodeID] so it doesn't get overwritten next iteration. + nodeID := nodeID - s.validatorState.DeleteValidatorMetadata(nodeID, subnetID) + var ( + staker *Staker + pk *bls.PublicKey + weightDiff = &ValidatorWeightDiff{ + Decrease: validatorDiff.validatorStatus == deleted, + } + ) + if validatorDiff.validatorStatus != unmodified { + staker = validatorDiff.validator + + pk = staker.PublicKey + // For non-primary network validators, the public key is inherited + // from the primary network. + if subnetID != constants.PrimaryNetworkID { + if vdr, ok := s.currentStakers.validators[constants.PrimaryNetworkID][nodeID]; ok && vdr.validator != nil { + // The primary network validator is still present after + // writing. + pk = vdr.validator.PublicKey + } else if vdr, ok := s.currentStakers.validatorDiffs[constants.PrimaryNetworkID][nodeID]; ok && vdr.validator != nil { + // The primary network validator is being removed during + // writing. + pk = vdr.validator.PublicKey + } else { + // This should never happen. + return errors.New("missing primary network validator") + } } - err := writeCurrentDelegatorDiff( - delegatorDB, - weightDiff, - validatorDiff, - codecVersion, - ) - if err != nil { - return err + weightDiff.Amount = staker.Weight + } + + switch validatorDiff.validatorStatus { + case added: + if pk != nil { + // Record that the public key for the validator is being + // added. This means the prior value for the public key was + // nil. + err := s.validatorPublicKeyDiffsDB.Put( + marshalDiffKey(subnetID, height, nodeID), + nil, + ) + if err != nil { + return err + } } - if weightDiff.Amount == 0 { - // No weight change to record; go to next validator. - continue + // The validator is being added. + // + // Invariant: It's impossible for a delegator to have been + // rewarded in the same block that the validator was added. + startTime := uint64(staker.StartTime.Unix()) + metadata := &validatorMetadata{ + txID: staker.TxID, + lastUpdated: staker.StartTime, + + UpDuration: 0, + LastUpdated: startTime, + StakerStartTime: startTime, + PotentialReward: staker.PotentialReward, + PotentialDelegateeReward: 0, } - err = s.validatorWeightDiffsDB.Put( - marshalDiffKey(subnetID, height, nodeID), - marshalWeightDiff(weightDiff), - ) + metadataBytes, err := MetadataCodec.Marshal(codecVersion, metadata) if err != nil { - return err + return fmt.Errorf("failed to serialize current validator: %w", err) } - // TODO: Move the validator set management out of the state package - if !updateValidators { - continue + if err = validatorDB.Put(staker.TxID[:], metadataBytes); err != nil { + return fmt.Errorf("failed to write current validator to list: %w", err) } - if weightDiff.Decrease { - err = s.validators.RemoveWeight(subnetID, nodeID, weightDiff.Amount) - } else { - if validatorDiff.validatorStatus == added { - staker := validatorDiff.validator - err = s.validators.AddStaker( - subnetID, - nodeID, - staker.PublicKey, - staker.TxID, - weightDiff.Amount, - ) - } else { - err = s.validators.AddWeight(subnetID, nodeID, weightDiff.Amount) + s.validatorState.LoadValidatorMetadata(nodeID, subnetID, metadata) + case deleted: + if pk != nil { + // Record that the public key for the validator is being + // removed. This means we must record the prior value of the + // public key. + // + // Note: We store the uncompressed public key here as it is + // significantly more efficient to parse when applying + // diffs. + err := s.validatorPublicKeyDiffsDB.Put( + marshalDiffKey(subnetID, height, nodeID), + bls.PublicKeyToUncompressedBytes(pk), + ) + if err != nil { + return err } } - if err != nil { - return fmt.Errorf("failed to update validator weight: %w", err) + + if err := validatorDB.Delete(staker.TxID[:]); err != nil { + return fmt.Errorf("failed to delete current staker: %w", err) } + + s.validatorState.DeleteValidatorMetadata(nodeID, subnetID) } - } - // TODO: Move validator set management out of the state package - // - // Attempt to update the stake metrics - if !updateValidators { - return nil - } + err := writeCurrentDelegatorDiff( + delegatorDB, + weightDiff, + validatorDiff, + codecVersion, + ) + if err != nil { + return err + } - totalWeight, err := s.validators.TotalWeight(constants.PrimaryNetworkID) - if err != nil { - return fmt.Errorf("failed to get total weight of primary network: %w", err) - } + if weightDiff.Amount == 0 { + // No weight change to record; go to next validator. + continue + } - s.metrics.SetLocalStake(s.validators.GetWeight(constants.PrimaryNetworkID, s.ctx.NodeID)) - s.metrics.SetTotalStake(totalWeight) + err = s.validatorWeightDiffsDB.Put( + marshalDiffKey(subnetID, height, nodeID), + marshalWeightDiff(weightDiff), + ) + if err != nil { + return err + } + + // TODO: Move the validator set management out of the state package + if !updateValidators { + continue + } + + if weightDiff.Decrease { + err = s.validators.RemoveWeight(subnetID, nodeID, weightDiff.Amount) + } else { + if validatorDiff.validatorStatus == added { + err = s.validators.AddStaker( + subnetID, + nodeID, + pk, + staker.TxID, + weightDiff.Amount, + ) + } else { + err = s.validators.AddWeight(subnetID, nodeID, weightDiff.Amount) + } + } + if err != nil { + return fmt.Errorf("failed to update validator weight: %w", err) + } + } return nil } diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 5c79594efbd1..f093b90a587c 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -5,7 +5,6 @@ package state import ( "context" - "fmt" "maps" "math" "math/rand" @@ -30,6 +29,7 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/maybe" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/utils/wrappers" @@ -112,620 +112,340 @@ func TestStateSyncGenesis(t *testing.T) { ) } -// Whenever we store a staker, a whole bunch a data structures are updated +// Whenever we store a staker, a whole bunch of data structures are updated // This test is meant to capture which updates are carried out func TestPersistStakers(t *testing.T) { - tests := map[string]struct { - // Insert or delete a staker to state and store it - storeStaker func(*require.Assertions, ids.ID /*=subnetID*/, *state) *Staker - - // Check that the staker is duly stored/removed in P-chain state - checkStakerInState func(*require.Assertions, *state, *Staker) + const ( + primaryValidatorDuration = 28 * 24 * time.Hour + primaryDelegatorDuration = 14 * 24 * time.Hour + subnetValidatorDuration = 21 * 24 * time.Hour + subnetDelegatorDuration = 14 * 24 * time.Hour + + primaryValidatorReward = iota + primaryDelegatorReward + ) + var ( + primaryValidatorStartTime = time.Now().Truncate(time.Second) + primaryValidatorEndTime = primaryValidatorStartTime.Add(primaryValidatorDuration) + primaryValidatorEndTimeUnix = uint64(primaryValidatorEndTime.Unix()) + + primaryDelegatorStartTime = primaryValidatorStartTime + primaryDelegatorEndTime = primaryDelegatorStartTime.Add(primaryDelegatorDuration) + primaryDelegatorEndTimeUnix = uint64(primaryDelegatorEndTime.Unix()) + + primaryValidatorData = txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + End: primaryValidatorEndTimeUnix, + Wght: 1234, + } + primaryDelegatorData = txs.Validator{ + NodeID: primaryValidatorData.NodeID, + End: primaryDelegatorEndTimeUnix, + Wght: 6789, + } + ) - // Check whether validators are duly reported in the validator set, - // with the right weight and showing the BLS key - checkValidatorsSet func(*require.Assertions, *state, *Staker) + unsignedAddPrimaryNetworkValidator := createPermissionlessValidatorTx(t, constants.PrimaryNetworkID, primaryValidatorData) + addPrimaryNetworkValidator := &txs.Tx{Unsigned: unsignedAddPrimaryNetworkValidator} + require.NoError(t, addPrimaryNetworkValidator.Initialize(txs.Codec)) - // Check that node duly track stakers uptimes - checkValidatorUptimes func(*require.Assertions, *state, *Staker) + primaryNetworkPendingValidatorStaker, err := NewPendingStaker( + addPrimaryNetworkValidator.ID(), + unsignedAddPrimaryNetworkValidator, + ) + require.NoError(t, err) - // Check whether weight/bls keys diffs are duly stored - checkDiffs func(*require.Assertions, *state, *Staker, uint64) - }{ - "add current validator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - var ( - startTime = time.Now().Unix() - endTime = time.Now().Add(14 * 24 * time.Hour).Unix() + primaryNetworkCurrentValidatorStaker, err := NewCurrentStaker( + addPrimaryNetworkValidator.ID(), + unsignedAddPrimaryNetworkValidator, + primaryValidatorStartTime, + primaryValidatorReward, + ) + require.NoError(t, err) - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(endTime), - Wght: 1234, - } - validatorReward uint64 = 5678 - ) + unsignedAddPrimaryNetworkDelegator := createPermissionlessDelegatorTx(constants.PrimaryNetworkID, primaryDelegatorData) + addPrimaryNetworkDelegator := &txs.Tx{Unsigned: unsignedAddPrimaryNetworkDelegator} + require.NoError(t, addPrimaryNetworkDelegator.Initialize(txs.Codec)) - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) + primaryNetworkPendingDelegatorStaker, err := NewPendingStaker( + addPrimaryNetworkDelegator.ID(), + unsignedAddPrimaryNetworkDelegator, + ) + require.NoError(t, err) - staker, err := NewCurrentStaker( - addPermValTx.ID(), - utx, - time.Unix(startTime, 0), - validatorReward, - ) - r.NoError(err) - - r.NoError(s.PutCurrentValidator(staker)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - return staker - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - retrievedStaker, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.Equal(staker, retrievedStaker) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - valsMap := s.validators.GetMap(staker.SubnetID) - r.Contains(valsMap, staker.NodeID) - r.Equal( - &validators.GetValidatorOutput{ - NodeID: staker.NodeID, - PublicKey: staker.PublicKey, - Weight: staker.Weight, - }, - valsMap[staker.NodeID], - ) - }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - upDuration, lastUpdated, err := s.GetUptime(staker.NodeID) - if staker.SubnetID != constants.PrimaryNetworkID { - // only primary network validators have uptimes - r.ErrorIs(err, database.ErrNotFound) - } else { - r.NoError(err) - r.Equal(upDuration, time.Duration(0)) - r.Equal(lastUpdated, staker.StartTime) - } - }, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.NoError(err) - weightDiff, err := unmarshalWeightDiff(weightDiffBytes) - r.NoError(err) - r.Equal(&ValidatorWeightDiff{ - Decrease: false, - Amount: staker.Weight, - }, weightDiff) - - blsDiffBytes, err := s.validatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - if staker.SubnetID == constants.PrimaryNetworkID { - r.NoError(err) - r.Nil(blsDiffBytes) - } else { - r.ErrorIs(err, database.ErrNotFound) - } - }, - }, - "add current delegator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // insert the delegator and its validator - var ( - valStartTime = time.Now().Truncate(time.Second).Unix() - delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() - delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() - valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(valEndTime), - Wght: 1234, - } - validatorReward uint64 = 5678 + primaryNetworkCurrentDelegatorStaker, err := NewCurrentStaker( + addPrimaryNetworkDelegator.ID(), + unsignedAddPrimaryNetworkDelegator, + primaryDelegatorStartTime, + primaryDelegatorReward, + ) + require.NoError(t, err) - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, - } - delegatorReward uint64 = 5432 - ) + tests := map[string]struct { + initialStakers []*Staker + initialTxs []*txs.Tx - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) + // Staker to insert or remove + staker *Staker + tx *txs.Tx // If tx is nil, the staker is being removed - val, err := NewCurrentStaker( - addPermValTx.ID(), - utxVal, - time.Unix(valStartTime, 0), - validatorReward, - ) - r.NoError(err) + // Check that the staker is duly stored/removed in P-chain state + expectedCurrentValidator *Staker + expectedPendingValidator *Staker + expectedCurrentDelegators []*Staker + expectedPendingDelegators []*Staker - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) + // Check that the validator entry has been set correctly in the + // in-memory validator set. + expectedValidatorSetOutput *validators.GetValidatorOutput - del, err := NewCurrentStaker( - addPermDelTx.ID(), - utxDel, - time.Unix(delStartTime, 0), - delegatorReward, - ) - r.NoError(err) - - r.NoError(s.PutCurrentValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - - s.PutCurrentDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - return del - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - delIt, err := s.GetCurrentDelegatorIterator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.True(delIt.Next()) - retrievedDelegator := delIt.Value() - r.False(delIt.Next()) - delIt.Release() - r.Equal(staker, retrievedDelegator) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - val, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) - r.NoError(err) - - valsMap := s.validators.GetMap(staker.SubnetID) - r.Contains(valsMap, staker.NodeID) - valOut := valsMap[staker.NodeID] - r.Equal(valOut.NodeID, staker.NodeID) - r.Equal(valOut.Weight, val.Weight+staker.Weight) - }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - // validator's weight must increase of delegator's weight amount - weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.NoError(err) - weightDiff, err := unmarshalWeightDiff(weightDiffBytes) - r.NoError(err) - r.Equal(&ValidatorWeightDiff{ - Decrease: false, - Amount: staker.Weight, - }, weightDiff) + // Check whether weight/bls keys diffs are duly stored + expectedWeightDiff *ValidatorWeightDiff + expectedPublicKeyDiff maybe.Maybe[*bls.PublicKey] + }{ + "add current primary network validator": { + staker: primaryNetworkCurrentValidatorStaker, + tx: addPrimaryNetworkValidator, + expectedCurrentValidator: primaryNetworkCurrentValidatorStaker, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: primaryNetworkCurrentValidatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: primaryNetworkCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: false, + Amount: primaryNetworkCurrentValidatorStaker.Weight, }, + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](nil), }, - "add pending validator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - var ( - startTime = time.Now().Unix() - endTime = time.Now().Add(14 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: uint64(startTime), - End: uint64(endTime), - Wght: 1234, - } - ) - - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - staker, err := NewPendingStaker( - addPermValTx.ID(), - utx, - ) - r.NoError(err) - - r.NoError(s.PutPendingValidator(staker)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - return staker + "add current primary network delegator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkCurrentDelegatorStaker, + tx: addPrimaryNetworkDelegator, + expectedCurrentValidator: primaryNetworkCurrentValidatorStaker, + expectedCurrentDelegators: []*Staker{primaryNetworkCurrentDelegatorStaker}, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: primaryNetworkCurrentDelegatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: primaryNetworkCurrentDelegatorStaker.Weight + primaryNetworkCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: false, + Amount: primaryNetworkCurrentDelegatorStaker.Weight, }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - retrievedStaker, err := s.GetPendingValidator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.Equal(staker, retrievedStaker) + }, + "add pending primary network validator": { + staker: primaryNetworkPendingValidatorStaker, + tx: addPrimaryNetworkValidator, + expectedPendingValidator: primaryNetworkPendingValidatorStaker, + }, + "add pending primary network delegator": { + initialStakers: []*Staker{primaryNetworkPendingValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkPendingDelegatorStaker, + tx: addPrimaryNetworkDelegator, + expectedPendingValidator: primaryNetworkPendingValidatorStaker, + expectedPendingDelegators: []*Staker{primaryNetworkPendingDelegatorStaker}, + }, + "delete current primary network validator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkCurrentValidatorStaker, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: true, + Amount: primaryNetworkCurrentValidatorStaker.Weight, }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - // pending validators are not showed in validators set - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) + expectedPublicKeyDiff: maybe.Some(primaryNetworkCurrentValidatorStaker.PublicKey), + }, + "delete current primary network delegator": { + initialStakers: []*Staker{ + primaryNetworkCurrentValidatorStaker, + primaryNetworkCurrentDelegatorStaker, + }, + initialTxs: []*txs.Tx{ + addPrimaryNetworkValidator, + addPrimaryNetworkDelegator, + }, + staker: primaryNetworkCurrentDelegatorStaker, + expectedCurrentValidator: primaryNetworkCurrentValidatorStaker, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: primaryNetworkCurrentValidatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: primaryNetworkCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: true, + Amount: primaryNetworkCurrentDelegatorStaker.Weight, }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - // pending validators uptime is not tracked - _, _, err := s.GetUptime(staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) + }, + "delete pending primary network validator": { + initialStakers: []*Staker{primaryNetworkPendingValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkPendingValidatorStaker, + }, + "delete pending primary network delegator": { + initialStakers: []*Staker{ + primaryNetworkPendingValidatorStaker, + primaryNetworkPendingDelegatorStaker, }, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - // pending validators weight diff and bls diffs are not stored - _, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.ErrorIs(err, database.ErrNotFound) - - _, err = s.validatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.ErrorIs(err, database.ErrNotFound) + initialTxs: []*txs.Tx{ + addPrimaryNetworkValidator, + addPrimaryNetworkDelegator, }, + staker: primaryNetworkPendingDelegatorStaker, + expectedPendingValidator: primaryNetworkPendingValidatorStaker, }, - "add pending delegator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // insert the delegator and its validator - var ( - valStartTime = time.Now().Truncate(time.Second).Unix() - delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() - delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() - valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: uint64(valStartTime), - End: uint64(valEndTime), - Wght: 1234, - } - - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - Start: uint64(delStartTime), - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, - } - ) + } - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) + for name, test := range tests { + t.Run(name, func(t *testing.T) { + require := require.New(t) - val, err := NewPendingStaker(addPermValTx.ID(), utxVal) - r.NoError(err) + db := memdb.New() + state := newTestState(t, db) - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) + // create and store the initial stakers + for _, staker := range test.initialStakers { + switch { + case staker.Priority.IsCurrentValidator(): + require.NoError(state.PutCurrentValidator(staker)) + case staker.Priority.IsPendingValidator(): + require.NoError(state.PutPendingValidator(staker)) + case staker.Priority.IsCurrentDelegator(): + state.PutCurrentDelegator(staker) + case staker.Priority.IsPendingDelegator(): + state.PutPendingDelegator(staker) + } + } + for _, tx := range test.initialTxs { + state.AddTx(tx, status.Committed) + } - del, err := NewPendingStaker(addPermDelTx.ID(), utxDel) - r.NoError(err) + state.SetHeight(0) + require.NoError(state.Commit()) - r.NoError(s.PutPendingValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + // create and store the staker under test + switch { + case test.staker.Priority.IsCurrentValidator(): + if test.tx != nil { + require.NoError(state.PutCurrentValidator(test.staker)) + } else { + state.DeleteCurrentValidator(test.staker) + } + case test.staker.Priority.IsPendingValidator(): + if test.tx != nil { + require.NoError(state.PutPendingValidator(test.staker)) + } else { + state.DeletePendingValidator(test.staker) + } + case test.staker.Priority.IsCurrentDelegator(): + if test.tx != nil { + state.PutCurrentDelegator(test.staker) + } else { + state.DeleteCurrentDelegator(test.staker) + } + case test.staker.Priority.IsPendingDelegator(): + if test.tx != nil { + state.PutPendingDelegator(test.staker) + } else { + state.DeletePendingDelegator(test.staker) + } + } + if test.tx != nil { + state.AddTx(test.tx, status.Committed) + } - s.PutPendingDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + state.SetHeight(1) + require.NoError(state.Commit()) - return del - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - delIt, err := s.GetPendingDelegatorIterator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.True(delIt.Next()) - retrievedDelegator := delIt.Value() - r.False(delIt.Next()) - delIt.Release() - r.Equal(staker, retrievedDelegator) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) - }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, - }, - "delete current validator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // add them remove the validator - var ( - startTime = time.Now().Unix() - endTime = time.Now().Add(14 * 24 * time.Hour).Unix() + // Perform the checks once immediately after committing to the + // state, and once after re-loading the state from disk. + for i := 0; i < 2; i++ { + currentValidator, err := state.GetCurrentValidator(test.staker.SubnetID, test.staker.NodeID) + if test.expectedCurrentValidator == nil { + require.ErrorIs(err, database.ErrNotFound) - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(endTime), - Wght: 1234, - } - validatorReward uint64 = 5678 - ) + // Only current validators should have uptimes + _, _, err := state.GetUptime(test.staker.NodeID) + require.ErrorIs(err, database.ErrNotFound) + } else { + require.NoError(err) + require.Equal(test.expectedCurrentValidator, currentValidator) - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) + // Current validators should also have uptimes + upDuration, lastUpdated, err := state.GetUptime(currentValidator.NodeID) + require.NoError(err) + require.Zero(upDuration) + require.Equal(currentValidator.StartTime, lastUpdated) + } - staker, err := NewCurrentStaker( - addPermValTx.ID(), - utx, - time.Unix(startTime, 0), - validatorReward, - ) - r.NoError(err) - - r.NoError(s.PutCurrentValidator(staker)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - - s.DeleteCurrentValidator(staker) - r.NoError(s.Commit()) - return staker - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - _, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - // deleted validators are not showed in the validators set anymore - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) - }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - // uptimes of delete validators are dropped - _, _, err := s.GetUptime(staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) - }, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.NoError(err) - weightDiff, err := unmarshalWeightDiff(weightDiffBytes) - r.NoError(err) - r.Equal(&ValidatorWeightDiff{ - Decrease: true, - Amount: staker.Weight, - }, weightDiff) - - blsDiffBytes, err := s.validatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - if staker.SubnetID == constants.PrimaryNetworkID { - r.NoError(err) - r.Equal(bls.PublicKeyFromValidUncompressedBytes(blsDiffBytes), staker.PublicKey) + pendingValidator, err := state.GetPendingValidator(test.staker.SubnetID, test.staker.NodeID) + if test.expectedPendingValidator == nil { + require.ErrorIs(err, database.ErrNotFound) } else { - r.ErrorIs(err, database.ErrNotFound) + require.NoError(err) + require.Equal(test.expectedPendingValidator, pendingValidator) } - }, - }, - "delete current delegator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // insert validator and delegator, then remove the delegator - var ( - valStartTime = time.Now().Truncate(time.Second).Unix() - delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() - delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() - valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(valEndTime), - Wght: 1234, - } - validatorReward uint64 = 5678 - - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, - } - delegatorReward uint64 = 5432 - ) - - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) - val, err := NewCurrentStaker( - addPermValTx.ID(), - utxVal, - time.Unix(valStartTime, 0), - validatorReward, - ) - r.NoError(err) - - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) - - del, err := NewCurrentStaker( - addPermDelTx.ID(), - utxDel, - time.Unix(delStartTime, 0), - delegatorReward, - ) - r.NoError(err) - - r.NoError(s.PutCurrentValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - - s.PutCurrentDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - - s.DeleteCurrentDelegator(del) - r.NoError(s.Commit()) - - return del - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - delIt, err := s.GetCurrentDelegatorIterator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.False(delIt.Next()) - delIt.Release() - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - val, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) - r.NoError(err) - - valsMap := s.validators.GetMap(staker.SubnetID) - r.Contains(valsMap, staker.NodeID) - valOut := valsMap[staker.NodeID] - r.Equal(valOut.NodeID, staker.NodeID) - r.Equal(valOut.Weight, val.Weight) - }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - // validator's weight must decrease of delegator's weight amount - weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.NoError(err) - weightDiff, err := unmarshalWeightDiff(weightDiffBytes) - r.NoError(err) - r.Equal(&ValidatorWeightDiff{ - Decrease: true, - Amount: staker.Weight, - }, weightDiff) - }, - }, - "delete pending validator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - var ( - startTime = time.Now().Unix() - endTime = time.Now().Add(14 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: uint64(startTime), - End: uint64(endTime), - Wght: 1234, - } + it, err := state.GetCurrentDelegatorIterator(test.staker.SubnetID, test.staker.NodeID) + require.NoError(err) + require.Equal( + test.expectedCurrentDelegators, + iterator.ToSlice(it), ) - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - staker, err := NewPendingStaker( - addPermValTx.ID(), - utx, + it, err = state.GetPendingDelegatorIterator(test.staker.SubnetID, test.staker.NodeID) + require.NoError(err) + require.Equal( + test.expectedPendingDelegators, + iterator.ToSlice(it), ) - r.NoError(err) - - r.NoError(s.PutPendingValidator(staker)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - - s.DeletePendingValidator(staker) - r.NoError(s.Commit()) - return staker - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - _, err := s.GetPendingValidator(staker.SubnetID, staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) - }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - _, _, err := s.GetUptime(staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) - }, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - _, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.ErrorIs(err, database.ErrNotFound) - - _, err = s.validatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.ErrorIs(err, database.ErrNotFound) - }, - }, - "delete pending delegator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // insert validator and delegator the remove the validator - var ( - valStartTime = time.Now().Truncate(time.Second).Unix() - delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() - delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() - valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: uint64(valStartTime), - End: uint64(valEndTime), - Wght: 1234, - } - - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - Start: uint64(delStartTime), - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, - } + require.Equal( + test.expectedValidatorSetOutput, + state.validators.GetMap(test.staker.SubnetID)[test.staker.NodeID], ) - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - val, err := NewPendingStaker(addPermValTx.ID(), utxVal) - r.NoError(err) - - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) - - del, err := NewPendingStaker(addPermDelTx.ID(), utxDel) - r.NoError(err) - - r.NoError(s.PutPendingValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - - s.PutPendingDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - - s.DeletePendingDelegator(del) - r.NoError(s.Commit()) - return del - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - delIt, err := s.GetPendingDelegatorIterator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.False(delIt.Next()) - delIt.Release() - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) - }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, - }, - } - - subnetIDs := []ids.ID{constants.PrimaryNetworkID, ids.GenerateTestID()} - for _, subnetID := range subnetIDs { - for name, test := range tests { - t.Run(fmt.Sprintf("%s - subnetID %s", name, subnetID), func(t *testing.T) { - require := require.New(t) - - db := memdb.New() - state := newTestState(t, db) - - // create and store the staker - staker := test.storeStaker(require, subnetID, state) + diffKey := marshalDiffKey(test.staker.SubnetID, 1, test.staker.NodeID) + weightDiffBytes, err := state.validatorWeightDiffsDB.Get(diffKey) + if test.expectedWeightDiff == nil { + require.ErrorIs(err, database.ErrNotFound) + } else { + require.NoError(err) - // check all relevant data are stored - test.checkStakerInState(require, state, staker) - test.checkValidatorsSet(require, state, staker) - test.checkValidatorUptimes(require, state, staker) - test.checkDiffs(require, state, staker, 0 /*height*/) + weightDiff, err := unmarshalWeightDiff(weightDiffBytes) + require.NoError(err) + require.Equal(test.expectedWeightDiff, weightDiff) + } - // rebuild the state - rebuiltState := newTestState(t, db) + publicKeyDiffBytes, err := state.validatorPublicKeyDiffsDB.Get(diffKey) + if test.expectedPublicKeyDiff.IsNothing() { + require.ErrorIs(err, database.ErrNotFound) + } else if expectedPublicKeyDiff := test.expectedPublicKeyDiff.Value(); expectedPublicKeyDiff == nil { + require.NoError(err) + require.Empty(publicKeyDiffBytes) + } else { + require.NoError(err) + require.Equal(expectedPublicKeyDiff, bls.PublicKeyFromValidUncompressedBytes(publicKeyDiffBytes)) + } - // check again that all relevant data are still available in rebuilt state - test.checkStakerInState(require, rebuiltState, staker) - test.checkValidatorsSet(require, rebuiltState, staker) - test.checkValidatorUptimes(require, rebuiltState, staker) - test.checkDiffs(require, rebuiltState, staker, 0 /*height*/) - }) - } + // re-load the state from disk + state = newTestState(t, db) + } + }) } } -func createPermissionlessValidatorTx(r *require.Assertions, subnetID ids.ID, validatorsData txs.Validator) *txs.AddPermissionlessValidatorTx { +func createPermissionlessValidatorTx( + t testing.TB, + subnetID ids.ID, + validatorsData txs.Validator, +) *txs.AddPermissionlessValidatorTx { var sig signer.Signer = &signer.Empty{} if subnetID == constants.PrimaryNetworkID { sk, err := bls.NewSecretKey() - r.NoError(err) + require.NoError(t, err) sig = signer.NewProofOfPossession(sk) } @@ -999,35 +719,43 @@ func TestStateAddRemoveValidator(t *testing.T) { state := newTestState(t, memdb.New()) var ( - numNodes = 3 - subnetID = ids.GenerateTestID() - startTime = time.Now() - endTime = startTime.Add(24 * time.Hour) - stakers = make([]Staker, numNodes) + numNodes = 5 + subnetID = ids.GenerateTestID() + startTime = time.Now() + endTime = startTime.Add(24 * time.Hour) + primaryStakers = make([]Staker, numNodes) + subnetStakers = make([]Staker, numNodes) ) - for i := 0; i < numNodes; i++ { - stakers[i] = Staker{ + for i := range primaryStakers { + sk, err := bls.NewSecretKey() + require.NoError(err) + + primaryStakers[i] = Staker{ TxID: ids.GenerateTestID(), NodeID: ids.GenerateTestNodeID(), + PublicKey: bls.PublicFromSecretKey(sk), + SubnetID: constants.PrimaryNetworkID, Weight: uint64(i + 1), StartTime: startTime.Add(time.Duration(i) * time.Second), EndTime: endTime.Add(time.Duration(i) * time.Second), PotentialReward: uint64(i + 1), } - if i%2 == 0 { - stakers[i].SubnetID = subnetID - } else { - sk, err := bls.NewSecretKey() - require.NoError(err) - stakers[i].PublicKey = bls.PublicFromSecretKey(sk) - stakers[i].SubnetID = constants.PrimaryNetworkID + } + for i, primaryStaker := range primaryStakers { + subnetStakers[i] = Staker{ + TxID: ids.GenerateTestID(), + NodeID: primaryStaker.NodeID, + PublicKey: nil, // Key is inherited from the primary network + SubnetID: subnetID, + Weight: uint64(i + 1), + StartTime: primaryStaker.StartTime, + EndTime: primaryStaker.EndTime, + PotentialReward: uint64(i + 1), } } type diff struct { addedValidators []Staker - addedDelegators []Staker - removedDelegators []Staker removedValidators []Staker expectedPrimaryValidatorSet map[ids.NodeID]*validators.GetValidatorOutput @@ -1040,101 +768,174 @@ func TestStateAddRemoveValidator(t *testing.T) { expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, { - // Add a subnet validator - addedValidators: []Staker{stakers[0]}, - expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, - expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[0].NodeID: { - NodeID: stakers[0].NodeID, - Weight: stakers[0].Weight, + // Add primary validator 0 + addedValidators: []Staker{primaryStakers[0]}, + expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + primaryStakers[0].NodeID: { + NodeID: primaryStakers[0].NodeID, + PublicKey: primaryStakers[0].PublicKey, + Weight: primaryStakers[0].Weight, }, }, + expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, { - // Remove a subnet validator - removedValidators: []Staker{stakers[0]}, - expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, - expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, + // Add subnet validator 0 + addedValidators: []Staker{subnetStakers[0]}, + expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + primaryStakers[0].NodeID: { + NodeID: primaryStakers[0].NodeID, + PublicKey: primaryStakers[0].PublicKey, + Weight: primaryStakers[0].Weight, + }, + }, + expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + subnetStakers[0].NodeID: { + NodeID: subnetStakers[0].NodeID, + PublicKey: primaryStakers[0].PublicKey, + Weight: subnetStakers[0].Weight, + }, + }, }, - { // Add a primary network validator - addedValidators: []Staker{stakers[1]}, + { + // Remove subnet validator 0 + removedValidators: []Staker{subnetStakers[0]}, expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[1].NodeID: { - NodeID: stakers[1].NodeID, - PublicKey: stakers[1].PublicKey, - Weight: stakers[1].Weight, + primaryStakers[0].NodeID: { + NodeID: primaryStakers[0].NodeID, + PublicKey: primaryStakers[0].PublicKey, + Weight: primaryStakers[0].Weight, }, }, expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, { - // Do nothing + // Add primary network validator 1, and subnet validator 1 + addedValidators: []Staker{primaryStakers[1], subnetStakers[1]}, + // Remove primary network validator 0, and subnet validator 1 + removedValidators: []Staker{primaryStakers[0], subnetStakers[1]}, expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[1].NodeID: { - NodeID: stakers[1].NodeID, - PublicKey: stakers[1].PublicKey, - Weight: stakers[1].Weight, + primaryStakers[1].NodeID: { + NodeID: primaryStakers[1].NodeID, + PublicKey: primaryStakers[1].PublicKey, + Weight: primaryStakers[1].Weight, }, }, expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, - { // Remove a primary network validator - removedValidators: []Staker{stakers[1]}, - expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, - expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, + { + // Add primary network validator 2, and subnet validator 2 + addedValidators: []Staker{primaryStakers[2], subnetStakers[2]}, + // Remove primary network validator 1 + removedValidators: []Staker{primaryStakers[1]}, + expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + primaryStakers[2].NodeID: { + NodeID: primaryStakers[2].NodeID, + PublicKey: primaryStakers[2].PublicKey, + Weight: primaryStakers[2].Weight, + }, + }, + expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + subnetStakers[2].NodeID: { + NodeID: subnetStakers[2].NodeID, + PublicKey: primaryStakers[2].PublicKey, + Weight: subnetStakers[2].Weight, + }, + }, }, { - // Add 2 subnet validators and a primary network validator - addedValidators: []Staker{stakers[0], stakers[1], stakers[2]}, + // Add primary network and subnet validators 3 & 4 + addedValidators: []Staker{primaryStakers[3], primaryStakers[4], subnetStakers[3], subnetStakers[4]}, expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[1].NodeID: { - NodeID: stakers[1].NodeID, - PublicKey: stakers[1].PublicKey, - Weight: stakers[1].Weight, + primaryStakers[2].NodeID: { + NodeID: primaryStakers[2].NodeID, + PublicKey: primaryStakers[2].PublicKey, + Weight: primaryStakers[2].Weight, + }, + primaryStakers[3].NodeID: { + NodeID: primaryStakers[3].NodeID, + PublicKey: primaryStakers[3].PublicKey, + Weight: primaryStakers[3].Weight, + }, + primaryStakers[4].NodeID: { + NodeID: primaryStakers[4].NodeID, + PublicKey: primaryStakers[4].PublicKey, + Weight: primaryStakers[4].Weight, }, }, expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[0].NodeID: { - NodeID: stakers[0].NodeID, - Weight: stakers[0].Weight, + subnetStakers[2].NodeID: { + NodeID: subnetStakers[2].NodeID, + PublicKey: primaryStakers[2].PublicKey, + Weight: subnetStakers[2].Weight, + }, + subnetStakers[3].NodeID: { + NodeID: subnetStakers[3].NodeID, + PublicKey: primaryStakers[3].PublicKey, + Weight: subnetStakers[3].Weight, }, - stakers[2].NodeID: { - NodeID: stakers[2].NodeID, - Weight: stakers[2].Weight, + subnetStakers[4].NodeID: { + NodeID: subnetStakers[4].NodeID, + PublicKey: primaryStakers[4].PublicKey, + Weight: subnetStakers[4].Weight, }, }, }, { - // Remove 2 subnet validators and a primary network validator. - removedValidators: []Staker{stakers[0], stakers[1], stakers[2]}, + // Remove primary network and subnet validators 2 & 3 & 4 + removedValidators: []Staker{ + primaryStakers[2], primaryStakers[3], primaryStakers[4], + subnetStakers[2], subnetStakers[3], subnetStakers[4], + }, + expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, + expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, + }, + { + // Do nothing expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, } for currentIndex, diff := range diffs { + d, err := NewDiffOn(state) + require.NoError(err) + + var expectedValidators set.Set[subnetIDNodeID] for _, added := range diff.addedValidators { added := added - require.NoError(state.PutCurrentValidator(&added)) - } - for _, added := range diff.addedDelegators { - added := added - state.PutCurrentDelegator(&added) - } - for _, removed := range diff.removedDelegators { - removed := removed - state.DeleteCurrentDelegator(&removed) + require.NoError(d.PutCurrentValidator(&added)) + + expectedValidators.Add(subnetIDNodeID{ + subnetID: added.SubnetID, + nodeID: added.NodeID, + }) } for _, removed := range diff.removedValidators { removed := removed - state.DeleteCurrentValidator(&removed) + d.DeleteCurrentValidator(&removed) + + expectedValidators.Remove(subnetIDNodeID{ + subnetID: removed.SubnetID, + nodeID: removed.NodeID, + }) } + require.NoError(d.Apply(state)) + currentHeight := uint64(currentIndex + 1) state.SetHeight(currentHeight) require.NoError(state.Commit()) for _, added := range diff.addedValidators { + subnetNodeID := subnetIDNodeID{ + subnetID: added.SubnetID, + nodeID: added.NodeID, + } + if !expectedValidators.Contains(subnetNodeID) { + continue + } + gotValidator, err := state.GetCurrentValidator(added.SubnetID, added.NodeID) require.NoError(err) require.Equal(added, *gotValidator) @@ -1157,8 +958,6 @@ func TestStateAddRemoveValidator(t *testing.T) { prevHeight+1, constants.PrimaryNetworkID, )) - requireEqualWeightsValidatorSet(require, prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) - require.NoError(state.ApplyValidatorPublicKeyDiffs( context.Background(), primaryValidatorSet, @@ -1166,7 +965,7 @@ func TestStateAddRemoveValidator(t *testing.T) { prevHeight+1, constants.PrimaryNetworkID, )) - requireEqualPublicKeysValidatorSet(require, prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) + require.Equal(prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) subnetValidatorSet := copyValidatorSet(diff.expectedSubnetValidatorSet) require.NoError(state.ApplyValidatorWeightDiffs( @@ -1176,7 +975,14 @@ func TestStateAddRemoveValidator(t *testing.T) { prevHeight+1, subnetID, )) - requireEqualWeightsValidatorSet(require, prevDiff.expectedSubnetValidatorSet, subnetValidatorSet) + require.NoError(state.ApplyValidatorPublicKeyDiffs( + context.Background(), + subnetValidatorSet, + currentHeight, + prevHeight+1, + subnetID, + )) + require.Equal(prevDiff.expectedSubnetValidatorSet, subnetValidatorSet) } } } @@ -1192,36 +998,6 @@ func copyValidatorSet( return result } -func requireEqualWeightsValidatorSet( - require *require.Assertions, - expected map[ids.NodeID]*validators.GetValidatorOutput, - actual map[ids.NodeID]*validators.GetValidatorOutput, -) { - require.Len(actual, len(expected)) - for nodeID, expectedVdr := range expected { - require.Contains(actual, nodeID) - - actualVdr := actual[nodeID] - require.Equal(expectedVdr.NodeID, actualVdr.NodeID) - require.Equal(expectedVdr.Weight, actualVdr.Weight) - } -} - -func requireEqualPublicKeysValidatorSet( - require *require.Assertions, - expected map[ids.NodeID]*validators.GetValidatorOutput, - actual map[ids.NodeID]*validators.GetValidatorOutput, -) { - require.Len(actual, len(expected)) - for nodeID, expectedVdr := range expected { - require.Contains(actual, nodeID) - - actualVdr := actual[nodeID] - require.Equal(expectedVdr.NodeID, actualVdr.NodeID) - require.Equal(expectedVdr.PublicKey, actualVdr.PublicKey) - } -} - func TestParsedStateBlock(t *testing.T) { var ( require = require.New(t) diff --git a/vms/platformvm/validators/manager.go b/vms/platformvm/validators/manager.go index 02ff6e475ab2..feeb182dd84d 100644 --- a/vms/platformvm/validators/manager.go +++ b/vms/platformvm/validators/manager.go @@ -200,17 +200,7 @@ func (m *manager) GetValidatorSet( // get the start time to track metrics startTime := m.clk.Time() - - var ( - validatorSet map[ids.NodeID]*validators.GetValidatorOutput - currentHeight uint64 - err error - ) - if subnetID == constants.PrimaryNetworkID { - validatorSet, currentHeight, err = m.makePrimaryNetworkValidatorSet(ctx, targetHeight) - } else { - validatorSet, currentHeight, err = m.makeSubnetValidatorSet(ctx, targetHeight, subnetID) - } + validatorSet, currentHeight, err := m.makeValidatorSet(ctx, targetHeight, subnetID) if err != nil { return nil, err } @@ -243,65 +233,12 @@ func (m *manager) getValidatorSetCache(subnetID ids.ID) cache.Cacher[uint64, map return validatorSetsCache } -func (m *manager) makePrimaryNetworkValidatorSet( - ctx context.Context, - targetHeight uint64, -) (map[ids.NodeID]*validators.GetValidatorOutput, uint64, error) { - validatorSet, currentHeight, err := m.getCurrentPrimaryValidatorSet(ctx) - if err != nil { - return nil, 0, err - } - if currentHeight < targetHeight { - return nil, 0, fmt.Errorf("%w with SubnetID = %s: current P-chain height (%d) < requested P-Chain height (%d)", - errUnfinalizedHeight, - constants.PrimaryNetworkID, - currentHeight, - targetHeight, - ) - } - - // Rebuild primary network validators at [targetHeight] - // - // Note: Since we are attempting to generate the validator set at - // [targetHeight], we want to apply the diffs from - // (targetHeight, currentHeight]. Because the state interface is implemented - // to be inclusive, we apply diffs in [targetHeight + 1, currentHeight]. - lastDiffHeight := targetHeight + 1 - err = m.state.ApplyValidatorWeightDiffs( - ctx, - validatorSet, - currentHeight, - lastDiffHeight, - constants.PrimaryNetworkID, - ) - if err != nil { - return nil, 0, err - } - - err = m.state.ApplyValidatorPublicKeyDiffs( - ctx, - validatorSet, - currentHeight, - lastDiffHeight, - constants.PrimaryNetworkID, - ) - return validatorSet, currentHeight, err -} - -func (m *manager) getCurrentPrimaryValidatorSet( - ctx context.Context, -) (map[ids.NodeID]*validators.GetValidatorOutput, uint64, error) { - primaryMap := m.cfg.Validators.GetMap(constants.PrimaryNetworkID) - currentHeight, err := m.getCurrentHeight(ctx) - return primaryMap, currentHeight, err -} - -func (m *manager) makeSubnetValidatorSet( +func (m *manager) makeValidatorSet( ctx context.Context, targetHeight uint64, subnetID ids.ID, ) (map[ids.NodeID]*validators.GetValidatorOutput, uint64, error) { - subnetValidatorSet, primaryValidatorSet, currentHeight, err := m.getCurrentValidatorSets(ctx, subnetID) + subnetValidatorSet, currentHeight, err := m.getCurrentValidatorSet(ctx, subnetID) if err != nil { return nil, 0, err } @@ -332,38 +269,23 @@ func (m *manager) makeSubnetValidatorSet( return nil, 0, err } - // Update the subnet validator set to include the public keys at - // [currentHeight]. When we apply the public key diffs, we will convert - // these keys to represent the public keys at [targetHeight]. If the subnet - // validator is not currently a primary network validator, it doesn't have a - // key at [currentHeight]. - for nodeID, vdr := range subnetValidatorSet { - if primaryVdr, ok := primaryValidatorSet[nodeID]; ok { - vdr.PublicKey = primaryVdr.PublicKey - } else { - vdr.PublicKey = nil - } - } - - // Prior to ACP-77, public keys were inherited from the primary network. err = m.state.ApplyValidatorPublicKeyDiffs( ctx, subnetValidatorSet, currentHeight, lastDiffHeight, - constants.PrimaryNetworkID, + subnetID, ) return subnetValidatorSet, currentHeight, err } -func (m *manager) getCurrentValidatorSets( +func (m *manager) getCurrentValidatorSet( ctx context.Context, subnetID ids.ID, -) (map[ids.NodeID]*validators.GetValidatorOutput, map[ids.NodeID]*validators.GetValidatorOutput, uint64, error) { +) (map[ids.NodeID]*validators.GetValidatorOutput, uint64, error) { subnetMap := m.cfg.Validators.GetMap(subnetID) - primaryMap := m.cfg.Validators.GetMap(constants.PrimaryNetworkID) currentHeight, err := m.getCurrentHeight(ctx) - return subnetMap, primaryMap, currentHeight, err + return subnetMap, currentHeight, err } func (m *manager) GetSubnetID(_ context.Context, chainID ids.ID) (ids.ID, error) { From d9108a7d2600a92e3fddd230817c70235cb3bf30 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 23 Sep 2024 19:12:57 -0400 Subject: [PATCH 062/199] mocks --- vms/platformvm/state/mock_state.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index 4780ede0da5e..41d06025d3f8 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -542,6 +542,20 @@ func (mr *MockStateMockRecorder) GetRewardUTXOs(txID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRewardUTXOs", reflect.TypeOf((*MockState)(nil).GetRewardUTXOs), txID) } +// GetSoVExcess mocks base method. +func (m *MockState) GetSoVExcess() gas.Gas { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSoVExcess") + ret0, _ := ret[0].(gas.Gas) + return ret0 +} + +// GetSoVExcess indicates an expected call of GetSoVExcess. +func (mr *MockStateMockRecorder) GetSoVExcess() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSoVExcess", reflect.TypeOf((*MockState)(nil).GetSoVExcess)) +} + // GetStartTime mocks base method. func (m *MockState) GetStartTime(nodeID ids.NodeID) (time.Time, error) { m.ctrl.T.Helper() From 98020f1c41a8d793df55d10093cfacf078ece6cd Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 23 Sep 2024 22:22:35 -0400 Subject: [PATCH 063/199] Bound GetNextStakerTime --- vms/platformvm/block/builder/builder.go | 17 ++++--- .../block/executor/proposal_block_test.go | 5 ++- .../block/executor/standard_block_test.go | 2 +- vms/platformvm/block/executor/verifier.go | 18 +------- vms/platformvm/state/chain_time_helpers.go | 45 +++++++++---------- .../txs/executor/advance_time_test.go | 7 +-- .../txs/executor/proposal_tx_executor.go | 22 +-------- vms/platformvm/txs/executor/state_changes.go | 44 ++++++++++++------ .../txs/executor/state_changes_test.go | 3 +- 9 files changed, 77 insertions(+), 86 deletions(-) diff --git a/vms/platformvm/block/builder/builder.go b/vms/platformvm/block/builder/builder.go index 35ad20adc16c..aa4804cae6d1 100644 --- a/vms/platformvm/block/builder/builder.go +++ b/vms/platformvm/block/builder/builder.go @@ -31,9 +31,15 @@ import ( txexecutor "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" ) -// targetBlockSize is maximum number of transaction bytes to place into a -// StandardBlock -const targetBlockSize = 128 * units.KiB +const ( + // targetBlockSize is maximum number of transaction bytes to place into a + // StandardBlock + targetBlockSize = 128 * units.KiB + + // maxTimeToSleep is the maximum time to sleep between checking if a block + // should be produced. + maxTimeToSleep = time.Hour +) var ( _ Builder = (*builder)(nil) @@ -174,12 +180,13 @@ func (b *builder) durationToSleep() (time.Duration, error) { return 0, fmt.Errorf("%w: %s", errMissingPreferredState, preferredID) } - nextStakerChangeTime, err := state.GetNextStakerChangeTime(preferredState) + now := b.txExecutorBackend.Clk.Time() + maxTimeToAwake := now.Add(maxTimeToSleep) + nextStakerChangeTime, err := state.GetNextStakerChangeTime(preferredState, maxTimeToAwake) if err != nil { return 0, fmt.Errorf("%w of %s: %w", errCalculatingNextStakerTime, preferredID, err) } - now := b.txExecutorBackend.Clk.Time() return nextStakerChangeTime.Sub(now), nil } diff --git a/vms/platformvm/block/executor/proposal_block_test.go b/vms/platformvm/block/executor/proposal_block_test.go index 60520a83ccf2..41553c151309 100644 --- a/vms/platformvm/block/executor/proposal_block_test.go +++ b/vms/platformvm/block/executor/proposal_block_test.go @@ -21,6 +21,7 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/iterator/iteratormock" + "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/block" @@ -279,7 +280,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { block := env.blkManager.NewBlock(statelessProposalBlock) err = block.Verify(context.Background()) - require.ErrorIs(err, errChildBlockEarlierThanParent) + require.ErrorIs(err, executor.ErrChildBlockEarlierThanParent) } { @@ -1377,7 +1378,7 @@ func TestAddValidatorProposalBlock(t *testing.T) { // Advance time until next staker change time is [validatorEndTime] for { - nextStakerChangeTime, err := state.GetNextStakerChangeTime(env.state) + nextStakerChangeTime, err := state.GetNextStakerChangeTime(env.state, mockable.MaxTime) require.NoError(err) if nextStakerChangeTime.Equal(validatorEndTime) { break diff --git a/vms/platformvm/block/executor/standard_block_test.go b/vms/platformvm/block/executor/standard_block_test.go index fa64eee74697..065bd2098616 100644 --- a/vms/platformvm/block/executor/standard_block_test.go +++ b/vms/platformvm/block/executor/standard_block_test.go @@ -212,7 +212,7 @@ func TestBanffStandardBlockTimeVerification(t *testing.T) { require.NoError(err) block := env.blkManager.NewBlock(banffChildBlk) err = block.Verify(context.Background()) - require.ErrorIs(err, errChildBlockEarlierThanParent) + require.ErrorIs(err, executor.ErrChildBlockEarlierThanParent) } { diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index 532dc4d4b6f6..abcbc566a303 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -27,7 +27,6 @@ var ( errApricotBlockIssuedAfterFork = errors.New("apricot block issued after fork") errBanffStandardBlockWithoutChanges = errors.New("BanffStandardBlock performs no state changes") errIncorrectBlockHeight = errors.New("incorrect block height") - errChildBlockEarlierThanParent = errors.New("proposed timestamp before current chain time") errOptionBlockTimestampNotMatchingParent = errors.New("option block proposed timestamp not matching parent block one") ) @@ -278,26 +277,11 @@ func (v *verifier) banffNonOptionBlock(b block.BanffBlock) error { } newChainTime := b.Timestamp() - parentChainTime := parentState.GetTimestamp() - if newChainTime.Before(parentChainTime) { - return fmt.Errorf( - "%w: proposed timestamp (%s), chain time (%s)", - errChildBlockEarlierThanParent, - newChainTime, - parentChainTime, - ) - } - - nextStakerChangeTime, err := state.GetNextStakerChangeTime(parentState) - if err != nil { - return fmt.Errorf("could not verify block timestamp: %w", err) - } - now := v.txExecutorBackend.Clk.Time() return executor.VerifyNewChainTime( newChainTime, - nextStakerChangeTime, now, + parentState, ) } diff --git a/vms/platformvm/state/chain_time_helpers.go b/vms/platformvm/state/chain_time_helpers.go index 8b861f69a265..0a2fbfa8ba17 100644 --- a/vms/platformvm/state/chain_time_helpers.go +++ b/vms/platformvm/state/chain_time_helpers.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/config" @@ -24,7 +24,10 @@ func NextBlockTime(state Chain, clk *mockable.Clock) (time.Time, bool, error) { } // [timestamp] = max(now, parentTime) - nextStakerChangeTime, err := GetNextStakerChangeTime(state) + // If the NextStakerChangeTime is after timestamp, then we shouldn't return + // that the time was capped. + nextStakerChangeTimeCap := timestamp.Add(time.Second) + nextStakerChangeTime, err := GetNextStakerChangeTime(state, nextStakerChangeTimeCap) if err != nil { return time.Time{}, false, fmt.Errorf("failed getting next staker change time: %w", err) } @@ -39,37 +42,33 @@ func NextBlockTime(state Chain, clk *mockable.Clock) (time.Time, bool, error) { } // GetNextStakerChangeTime returns the next time a staker will be either added -// or removed to/from the current validator set. -func GetNextStakerChangeTime(state Chain) (time.Time, error) { - currentStakerIterator, err := state.GetCurrentStakerIterator() +// or removed to/from the current validator set. If the next staker change time +// is further in the future than [defaultTime], then [defaultTime] is returned. +func GetNextStakerChangeTime(state Chain, defaultTime time.Time) (time.Time, error) { + currentIterator, err := state.GetCurrentStakerIterator() if err != nil { return time.Time{}, err } - defer currentStakerIterator.Release() + defer currentIterator.Release() - pendingStakerIterator, err := state.GetPendingStakerIterator() + pendingIterator, err := state.GetPendingStakerIterator() if err != nil { return time.Time{}, err } - defer pendingStakerIterator.Release() + defer pendingIterator.Release() - hasCurrentStaker := currentStakerIterator.Next() - hasPendingStaker := pendingStakerIterator.Next() - switch { - case hasCurrentStaker && hasPendingStaker: - nextCurrentTime := currentStakerIterator.Value().NextTime - nextPendingTime := pendingStakerIterator.Value().NextTime - if nextCurrentTime.Before(nextPendingTime) { - return nextCurrentTime, nil + for _, it := range []iterator.Iterator[*Staker]{currentIterator, pendingIterator} { + // If the iterator is empty, skip it + if !it.Next() { + continue + } + + time := it.Value().NextTime + if time.Before(defaultTime) { + defaultTime = time } - return nextPendingTime, nil - case hasCurrentStaker: - return currentStakerIterator.Value().NextTime, nil - case hasPendingStaker: - return pendingStakerIterator.Value().NextTime, nil - default: - return time.Time{}, database.ErrNotFound } + return defaultTime, nil } // PickFeeCalculator creates either a static or a dynamic fee calculator, diff --git a/vms/platformvm/txs/executor/advance_time_test.go b/vms/platformvm/txs/executor/advance_time_test.go index c301b8bde8bb..5febc90e2cf9 100644 --- a/vms/platformvm/txs/executor/advance_time_test.go +++ b/vms/platformvm/txs/executor/advance_time_test.go @@ -99,12 +99,13 @@ func TestAdvanceTimeTxUpdatePrimaryNetworkStakers(t *testing.T) { require.True(ok) } -// Ensure semantic verification fails when proposed timestamp is at or before current timestamp +// Ensure semantic verification fails when proposed timestamp is before the +// current timestamp func TestAdvanceTimeTxTimestampTooEarly(t *testing.T) { require := require.New(t) env := newEnvironment(t, upgradetest.ApricotPhase5) - tx, err := newAdvanceTimeTx(t, env.state.GetTimestamp()) + tx, err := newAdvanceTimeTx(t, env.state.GetTimestamp().Add(-time.Second)) require.NoError(err) onCommitState, err := state.NewDiff(lastAcceptedID, env) @@ -122,7 +123,7 @@ func TestAdvanceTimeTxTimestampTooEarly(t *testing.T) { Tx: tx, } err = tx.Unsigned.Visit(&executor) - require.ErrorIs(err, ErrChildBlockNotAfterParent) + require.ErrorIs(err, ErrChildBlockEarlierThanParent) } // Ensure semantic verification fails when proposed timestamp is after next validator set change time diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go index 573d6199e2a3..aa8064f2fcd8 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor.go @@ -34,7 +34,6 @@ var ( ErrRemoveStakerTooEarly = errors.New("attempting to remove staker before their end time") ErrRemoveWrongStaker = errors.New("attempting to remove wrong staker") - ErrChildBlockNotAfterParent = errors.New("proposed timestamp not after current chain time") ErrInvalidState = errors.New("generated output isn't valid state") ErrShouldBePermissionlessStaker = errors.New("expected permissionless staker") ErrWrongTxType = errors.New("wrong transaction type") @@ -270,34 +269,17 @@ func (e *ProposalTxExecutor) AdvanceTimeTx(tx *txs.AdvanceTimeTx) error { ) } - parentChainTime := e.OnCommitState.GetTimestamp() - if !newChainTime.After(parentChainTime) { - return fmt.Errorf( - "%w, proposed timestamp (%s), chain time (%s)", - ErrChildBlockNotAfterParent, - parentChainTime, - parentChainTime, - ) - } - - // Only allow timestamp to move forward as far as the time of next staker - // set change time - nextStakerChangeTime, err := state.GetNextStakerChangeTime(e.OnCommitState) - if err != nil { - return err - } - now := e.Clk.Time() if err := VerifyNewChainTime( newChainTime, - nextStakerChangeTime, now, + e.OnCommitState, ); err != nil { return err } // Note that state doesn't change if this proposal is aborted - _, err = AdvanceTimeTo(e.Backend, e.OnCommitState, newChainTime) + _, err := AdvanceTimeTo(e.Backend, e.OnCommitState, newChainTime) return err } diff --git a/vms/platformvm/txs/executor/state_changes.go b/vms/platformvm/txs/executor/state_changes.go index 55f940fca509..04c11c5c2cd8 100644 --- a/vms/platformvm/txs/executor/state_changes.go +++ b/vms/platformvm/txs/executor/state_changes.go @@ -16,35 +16,35 @@ import ( ) var ( + ErrChildBlockEarlierThanParent = errors.New("proposed timestamp before current chain time") ErrChildBlockAfterStakerChangeTime = errors.New("proposed timestamp later than next staker change time") ErrChildBlockBeyondSyncBound = errors.New("proposed timestamp is too far in the future relative to local time") ) -// VerifyNewChainTime returns nil if the [newChainTime] is a valid chain time -// given the wall clock time ([now]) and when the next staking set change occurs -// ([nextStakerChangeTime]). +// VerifyNewChainTime returns nil if the [newChainTime] is a valid chain time. // Requires: -// - [newChainTime] <= [nextStakerChangeTime]: so that no staking set changes -// are skipped. +// - [newChainTime] >= [currentChainTime]: to ensure chain time advances +// monotonically. // - [newChainTime] <= [now] + [SyncBound]: to ensure chain time approximates // "real" time. +// - [newChainTime] <= [nextStakerChangeTime]: so that no staking set changes +// are skipped. func VerifyNewChainTime( - newChainTime, - nextStakerChangeTime, + newChainTime time.Time, now time.Time, + currentState state.Chain, ) error { - // Only allow timestamp to move as far forward as the time of the next - // staker set change - if newChainTime.After(nextStakerChangeTime) { + currentChainTime := currentState.GetTimestamp() + if newChainTime.Before(currentChainTime) { return fmt.Errorf( - "%w, proposed timestamp (%s), next staker change time (%s)", - ErrChildBlockAfterStakerChangeTime, + "%w: proposed timestamp (%s), chain time (%s)", + ErrChildBlockEarlierThanParent, newChainTime, - nextStakerChangeTime, + currentChainTime, ) } - // Only allow timestamp to reasonably far forward + // Only allow timestamp to be reasonably far forward maxNewChainTime := now.Add(SyncBound) if newChainTime.After(maxNewChainTime) { return fmt.Errorf( @@ -54,6 +54,22 @@ func VerifyNewChainTime( now, ) } + + nextStakerChangeTime, err := state.GetNextStakerChangeTime(currentState, newChainTime) + if err != nil { + return fmt.Errorf("could not verify block timestamp: %w", err) + } + + // Only allow timestamp to move as far forward as the time of the next + // staker set change + if newChainTime.After(nextStakerChangeTime) { + return fmt.Errorf( + "%w, proposed timestamp (%s), next staker change time (%s)", + ErrChildBlockAfterStakerChangeTime, + newChainTime, + nextStakerChangeTime, + ) + } return nil } diff --git a/vms/platformvm/txs/executor/state_changes_test.go b/vms/platformvm/txs/executor/state_changes_test.go index 5588f4b7da73..d642e2ed1481 100644 --- a/vms/platformvm/txs/executor/state_changes_test.go +++ b/vms/platformvm/txs/executor/state_changes_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/upgrade/upgradetest" + "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/config" "github.com/ava-labs/avalanchego/vms/platformvm/state" @@ -75,7 +76,7 @@ func TestAdvanceTimeTo_UpdatesFeeState(t *testing.T) { // Ensure the invariant that [nextTime <= nextStakerChangeTime] on // AdvanceTimeTo is maintained. - nextStakerChangeTime, err := state.GetNextStakerChangeTime(s) + nextStakerChangeTime, err := state.GetNextStakerChangeTime(s, mockable.MaxTime) require.NoError(err) require.False(nextTime.After(nextStakerChangeTime)) From cc9a08633ebc5cbc3e5cdb2bdab4a4e8cd17492b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 23 Sep 2024 23:08:08 -0400 Subject: [PATCH 064/199] fix test --- vms/platformvm/txs/executor/advance_time_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/txs/executor/advance_time_test.go b/vms/platformvm/txs/executor/advance_time_test.go index 5febc90e2cf9..fa7d3583c68f 100644 --- a/vms/platformvm/txs/executor/advance_time_test.go +++ b/vms/platformvm/txs/executor/advance_time_test.go @@ -126,7 +126,8 @@ func TestAdvanceTimeTxTimestampTooEarly(t *testing.T) { require.ErrorIs(err, ErrChildBlockEarlierThanParent) } -// Ensure semantic verification fails when proposed timestamp is after next validator set change time +// Ensure semantic verification fails when proposed timestamp is after next +// validator set change time func TestAdvanceTimeTxTimestampTooLate(t *testing.T) { require := require.New(t) env := newEnvironment(t, upgradetest.ApricotPhase5) @@ -167,8 +168,8 @@ func TestAdvanceTimeTxTimestampTooLate(t *testing.T) { env.ctx.Lock.Lock() defer env.ctx.Lock.Unlock() - // fast forward clock to 10 seconds before genesis validators stop validating - env.clk.Set(genesistest.DefaultValidatorEndTime.Add(-10 * time.Second)) + // fast forward clock to when genesis validators stop validating + env.clk.Set(genesistest.DefaultValidatorEndTime) { // Proposes advancing timestamp to 1 second after genesis validators stop validating From a3f0d94117a16025c6daa5381a928178b023f5c2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 23 Sep 2024 23:17:54 -0400 Subject: [PATCH 065/199] add comment --- vms/platformvm/txs/executor/state_changes.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vms/platformvm/txs/executor/state_changes.go b/vms/platformvm/txs/executor/state_changes.go index 04c11c5c2cd8..a30a422506b9 100644 --- a/vms/platformvm/txs/executor/state_changes.go +++ b/vms/platformvm/txs/executor/state_changes.go @@ -55,6 +55,8 @@ func VerifyNewChainTime( ) } + // nextStakerChangeTime is calculated last to ensure that the function is + // able to be calculated efficiently. nextStakerChangeTime, err := state.GetNextStakerChangeTime(currentState, newChainTime) if err != nil { return fmt.Errorf("could not verify block timestamp: %w", err) From de8151890acdf7a1e6ab2df104f7f81c25e89910 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 23 Sep 2024 23:50:06 -0400 Subject: [PATCH 066/199] Add tests --- .../state/chain_time_helpers_test.go | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/vms/platformvm/state/chain_time_helpers_test.go b/vms/platformvm/state/chain_time_helpers_test.go index 3a6304aaeea5..7e1a1786eb61 100644 --- a/vms/platformvm/state/chain_time_helpers_test.go +++ b/vms/platformvm/state/chain_time_helpers_test.go @@ -5,16 +5,131 @@ package state import ( "testing" + "time" "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/genesis" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/upgrade/upgradetest" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/platformvm/config" + "github.com/ava-labs/avalanchego/vms/platformvm/genesis/genesistest" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" ) +func TestNextBlockTime(t *testing.T) { + tests := []struct { + name string + chainTime time.Time + now time.Time + expectedTime time.Time + expectedCapped bool + }{ + { + name: "parent time is after now", + chainTime: genesistest.DefaultValidatorStartTime, + now: genesistest.DefaultValidatorStartTime.Add(-time.Second), + expectedTime: genesistest.DefaultValidatorStartTime, + expectedCapped: false, + }, + { + name: "parent time is before now", + chainTime: genesistest.DefaultValidatorStartTime, + now: genesistest.DefaultValidatorStartTime.Add(time.Second), + expectedTime: genesistest.DefaultValidatorStartTime.Add(time.Second), + expectedCapped: false, + }, + { + name: "now is at next staker change time", + chainTime: genesistest.DefaultValidatorStartTime, + now: genesistest.DefaultValidatorEndTime, + expectedTime: genesistest.DefaultValidatorEndTime, + expectedCapped: true, + }, + { + name: "now is after next staker change time", + chainTime: genesistest.DefaultValidatorStartTime, + now: genesistest.DefaultValidatorEndTime.Add(time.Second), + expectedTime: genesistest.DefaultValidatorEndTime, + expectedCapped: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var ( + require = require.New(t) + s = newTestState(t, memdb.New()) + clk mockable.Clock + ) + + s.SetTimestamp(test.chainTime) + clk.Set(test.now) + + actualTime, actualCapped, err := NextBlockTime(s, &clk) + require.NoError(err) + require.Equal(test.expectedTime.Local(), actualTime.Local()) + require.Equal(test.expectedCapped, actualCapped) + }) + } +} + +func TestGetNextStakerChangeTime(t *testing.T) { + tests := []struct { + name string + pending []*Staker + maxTime time.Time + expected time.Time + }{ + { + name: "only current validators", + maxTime: mockable.MaxTime, + expected: genesistest.DefaultValidatorEndTime, + }, + { + name: "current and pending validators", + pending: []*Staker{ + { + TxID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: nil, + SubnetID: constants.PrimaryNetworkID, + Weight: 1, + StartTime: genesistest.DefaultValidatorStartTime.Add(time.Second), + EndTime: genesistest.DefaultValidatorEndTime, + NextTime: genesistest.DefaultValidatorStartTime.Add(time.Second), + Priority: txs.PrimaryNetworkValidatorPendingPriority, + }, + }, + maxTime: mockable.MaxTime, + expected: genesistest.DefaultValidatorStartTime.Add(time.Second), + }, + { + name: "restricted timestamp", + maxTime: genesistest.DefaultValidatorStartTime, + expected: genesistest.DefaultValidatorStartTime, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var ( + require = require.New(t) + s = newTestState(t, memdb.New()) + ) + for _, staker := range test.pending { + require.NoError(s.PutPendingValidator(staker)) + } + + actual, err := GetNextStakerChangeTime(s, test.maxTime) + require.NoError(err) + require.Equal(test.expected.Local(), actual.Local()) + }) + } +} + func TestPickFeeCalculator(t *testing.T) { var ( createAssetTxFee = genesis.LocalParams.CreateAssetTxFee From 300d12cbc6ac02f9eb8682843443ad0ab560c6a5 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 24 Sep 2024 14:26:36 -0400 Subject: [PATCH 067/199] Deactivate SoVs without sufficient fees --- vms/platformvm/block/builder/builder.go | 18 +++- vms/platformvm/block/executor/manager.go | 6 +- .../block/executor/proposal_block_test.go | 6 +- vms/platformvm/block/executor/verifier.go | 69 ++++++++++++++-- .../block/executor/verifier_test.go | 6 +- vms/platformvm/state/chain_time_helpers.go | 82 +++++++++++++++---- .../txs/executor/proposal_tx_executor.go | 1 + vms/platformvm/txs/executor/state_changes.go | 75 ++++++++++++++--- .../txs/executor/state_changes_test.go | 5 +- 9 files changed, 226 insertions(+), 42 deletions(-) diff --git a/vms/platformvm/block/builder/builder.go b/vms/platformvm/block/builder/builder.go index aa4804cae6d1..3c88e8277929 100644 --- a/vms/platformvm/block/builder/builder.go +++ b/vms/platformvm/block/builder/builder.go @@ -182,7 +182,11 @@ func (b *builder) durationToSleep() (time.Duration, error) { now := b.txExecutorBackend.Clk.Time() maxTimeToAwake := now.Add(maxTimeToSleep) - nextStakerChangeTime, err := state.GetNextStakerChangeTime(preferredState, maxTimeToAwake) + nextStakerChangeTime, err := state.GetNextStakerChangeTime( + b.txExecutorBackend.Config.ValidatorFeeConfig, + preferredState, + maxTimeToAwake, + ) if err != nil { return 0, fmt.Errorf("%w of %s: %w", errCalculatingNextStakerTime, preferredID, err) } @@ -226,7 +230,11 @@ func (b *builder) BuildBlock(context.Context) (snowman.Block, error) { return nil, fmt.Errorf("%w: %s", state.ErrMissingParentState, preferredID) } - timestamp, timeWasCapped, err := state.NextBlockTime(preferredState, b.txExecutorBackend.Clk) + timestamp, timeWasCapped, err := state.NextBlockTime( + b.txExecutorBackend.Config.ValidatorFeeConfig, + preferredState, + b.txExecutorBackend.Clk, + ) if err != nil { return nil, fmt.Errorf("could not calculate next staker change time: %w", err) } @@ -253,7 +261,11 @@ func (b *builder) PackAllBlockTxs() ([]*txs.Tx, error) { return nil, fmt.Errorf("%w: %s", errMissingPreferredState, preferredID) } - timestamp, _, err := state.NextBlockTime(preferredState, b.txExecutorBackend.Clk) + timestamp, _, err := state.NextBlockTime( + b.txExecutorBackend.Config.ValidatorFeeConfig, + preferredState, + b.txExecutorBackend.Clk, + ) if err != nil { return nil, fmt.Errorf("could not calculate next staker change time: %w", err) } diff --git a/vms/platformvm/block/executor/manager.go b/vms/platformvm/block/executor/manager.go index 2cf3f1f988ff..5c419500e5f7 100644 --- a/vms/platformvm/block/executor/manager.go +++ b/vms/platformvm/block/executor/manager.go @@ -127,7 +127,11 @@ func (m *manager) VerifyTx(tx *txs.Tx) error { return err } - nextBlkTime, _, err := state.NextBlockTime(stateDiff, m.txExecutorBackend.Clk) + nextBlkTime, _, err := state.NextBlockTime( + m.txExecutorBackend.Config.ValidatorFeeConfig, + stateDiff, + m.txExecutorBackend.Clk, + ) if err != nil { return err } diff --git a/vms/platformvm/block/executor/proposal_block_test.go b/vms/platformvm/block/executor/proposal_block_test.go index 885a3d279638..3e0dfba90fb7 100644 --- a/vms/platformvm/block/executor/proposal_block_test.go +++ b/vms/platformvm/block/executor/proposal_block_test.go @@ -1382,7 +1382,11 @@ func TestAddValidatorProposalBlock(t *testing.T) { // Advance time until next staker change time is [validatorEndTime] for { - nextStakerChangeTime, err := state.GetNextStakerChangeTime(env.state, mockable.MaxTime) + nextStakerChangeTime, err := state.GetNextStakerChangeTime( + env.config.ValidatorFeeConfig, + env.state, + mockable.MaxTime, + ) require.NoError(err) if nextStakerChangeTime.Equal(validatorEndTime) { break diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index abcbc566a303..51a09b9cad61 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/block" @@ -17,6 +18,8 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + + validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) var ( @@ -279,6 +282,7 @@ func (v *verifier) banffNonOptionBlock(b block.BanffBlock) error { newChainTime := b.Timestamp() now := v.txExecutorBackend.Clk.Time() return executor.VerifyNewChainTime( + v.txExecutorBackend.Config.ValidatorFeeConfig, newChainTime, now, parentState, @@ -439,14 +443,16 @@ func (v *verifier) standardBlock( return nil } -func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator fee.Calculator, state state.Diff, parentID ids.ID) ( +func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator fee.Calculator, diff state.Diff, parentID ids.ID) ( set.Set[ids.ID], map[ids.ID]*atomic.Requests, func(), error, ) { // Complexity is limited first to avoid processing too large of a block. - if timestamp := state.GetTimestamp(); v.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) { + timestamp := diff.GetTimestamp() + isEtna := v.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) + if isEtna { var blockComplexity gas.Dimensions for _, tx := range txs { txComplexity, err := fee.TxComplexity(tx.Unsigned) @@ -469,7 +475,7 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator fee.Calculato // If this block exceeds the available capacity, ConsumeGas will return // an error. - feeState := state.GetFeeState() + feeState := diff.GetFeeState() feeState, err = feeState.ConsumeGas(blockGas) if err != nil { return nil, nil, nil, err @@ -477,7 +483,7 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator fee.Calculato // Updating the fee state prior to executing the transactions is fine // because the fee calculator was already created. - state.SetFeeState(feeState) + diff.SetFeeState(feeState) } var ( @@ -489,7 +495,7 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator fee.Calculato for _, tx := range txs { txExecutor := executor.StandardTxExecutor{ Backend: v.txExecutorBackend, - State: state, + State: diff, FeeCalculator: feeCalculator, Tx: tx, } @@ -505,7 +511,7 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator fee.Calculato // Add UTXOs to batch inputs.Union(txExecutor.Inputs) - state.AddTx(tx, status.Committed) + diff.AddTx(tx, status.Committed) if txExecutor.OnAccept != nil { funcs = append(funcs, txExecutor.OnAccept) } @@ -523,6 +529,57 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator fee.Calculato } } + // After processing all the transactions, deactivate any SoVs that might not + // have sufficient fee to pay for the next second. + // + // This ensures that SoVs are not undercharged for the next second. + if isEtna { + var ( + validatorFeeState = validatorfee.State{ + Current: gas.Gas(diff.NumActiveSubnetOnlyValidators()), + Excess: diff.GetSoVExcess(), + } + accruedFees = diff.GetAccruedFees() + potentialCost = validatorFeeState.CostOf( + v.txExecutorBackend.Config.ValidatorFeeConfig, + 1, + ) + ) + potentialAccruedFees, err := math.Add(accruedFees, potentialCost) + if err != nil { + return nil, nil, nil, err + } + + // TODO: Remove SoVs that don't have sufficient fee to pay for the next + // second. + // Invariant: Proposal transactions do not impact SoV state. + sovIterator, err := diff.GetActiveSubnetOnlyValidatorsIterator() + if err != nil { + return nil, nil, nil, err + } + + var sovsToDeactivate []state.SubnetOnlyValidator + for sovIterator.Next() { + sov := sovIterator.Value() + if sov.EndAccumulatedFee > potentialAccruedFees { + break + } + + sovsToDeactivate = append(sovsToDeactivate, sov) + } + + // The iterator must be released prior to attempting to write to the + // diff. + sovIterator.Release() + + for _, sov := range sovsToDeactivate { + sov.EndAccumulatedFee = 0 + if err := diff.PutSubnetOnlyValidator(sov); err != nil { + return nil, nil, nil, err + } + } + } + if err := v.verifyUniqueInputs(parentID, inputs); err != nil { return nil, nil, nil, err } diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 9b678e95cde9..2aa5e3ade0f6 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -1167,7 +1167,11 @@ func TestBlockExecutionWithComplexity(t *testing.T) { clear(verifier.blkIDToState) verifier.txExecutorBackend.Clk.Set(test.timestamp) - timestamp, _, err := state.NextBlockTime(s, verifier.txExecutorBackend.Clk) + timestamp, _, err := state.NextBlockTime( + verifier.txExecutorBackend.Config.ValidatorFeeConfig, + s, + verifier.txExecutorBackend.Clk, + ) require.NoError(err) lastAcceptedID := s.GetLastAccepted() diff --git a/vms/platformvm/state/chain_time_helpers.go b/vms/platformvm/state/chain_time_helpers.go index 0a2fbfa8ba17..97c2da561363 100644 --- a/vms/platformvm/state/chain_time_helpers.go +++ b/vms/platformvm/state/chain_time_helpers.go @@ -8,13 +8,20 @@ import ( "time" "github.com/ava-labs/avalanchego/utils/iterator" + "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/config" - "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + + txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) -func NextBlockTime(state Chain, clk *mockable.Clock) (time.Time, bool, error) { +func NextBlockTime( + config validatorfee.Config, + state Chain, + clk *mockable.Clock, +) (time.Time, bool, error) { var ( timestamp = clk.Time() parentTime = state.GetTimestamp() @@ -27,7 +34,7 @@ func NextBlockTime(state Chain, clk *mockable.Clock) (time.Time, bool, error) { // If the NextStakerChangeTime is after timestamp, then we shouldn't return // that the time was capped. nextStakerChangeTimeCap := timestamp.Add(time.Second) - nextStakerChangeTime, err := GetNextStakerChangeTime(state, nextStakerChangeTimeCap) + nextStakerChangeTime, err := GetNextStakerChangeTime(config, state, nextStakerChangeTimeCap) if err != nil { return time.Time{}, false, fmt.Errorf("failed getting next staker change time: %w", err) } @@ -44,7 +51,11 @@ func NextBlockTime(state Chain, clk *mockable.Clock) (time.Time, bool, error) { // GetNextStakerChangeTime returns the next time a staker will be either added // or removed to/from the current validator set. If the next staker change time // is further in the future than [defaultTime], then [defaultTime] is returned. -func GetNextStakerChangeTime(state Chain, defaultTime time.Time) (time.Time, error) { +func GetNextStakerChangeTime( + config validatorfee.Config, + state Chain, + defaultTime time.Time, +) (time.Time, error) { currentIterator, err := state.GetCurrentStakerIterator() if err != nil { return time.Time{}, err @@ -68,6 +79,43 @@ func GetNextStakerChangeTime(state Chain, defaultTime time.Time) (time.Time, err defaultTime = time } } + + sovIterator, err := state.GetActiveSubnetOnlyValidatorsIterator() + if err != nil { + return time.Time{}, err + } + defer sovIterator.Release() + + // If there are no SoVs, return + if !sovIterator.Next() { + return defaultTime, nil + } + + var ( + currentTime = state.GetTimestamp() + maxSeconds = uint64(defaultTime.Sub(currentTime) / time.Second) + sov = sovIterator.Value() + accruedFees = state.GetAccruedFees() + ) + remainingFunds, err := math.Sub(sov.EndAccumulatedFee, accruedFees) + if err != nil { + return time.Time{}, err + } + + feeState := validatorfee.State{ + Current: gas.Gas(state.NumActiveSubnetOnlyValidators()), + Excess: state.GetSoVExcess(), + } + remainingSeconds := feeState.SecondsRemaining( + config, + maxSeconds, + remainingFunds, + ) + + deactivationTime := currentTime.Add(time.Duration(remainingSeconds) * time.Second) + if deactivationTime.Before(defaultTime) { + defaultTime = deactivationTime + } return defaultTime, nil } @@ -75,31 +123,31 @@ func GetNextStakerChangeTime(state Chain, defaultTime time.Time) (time.Time, err // depending on the active upgrade. // // PickFeeCalculator does not modify [state]. -func PickFeeCalculator(cfg *config.Config, state Chain) fee.Calculator { +func PickFeeCalculator(config *config.Config, state Chain) txfee.Calculator { timestamp := state.GetTimestamp() - if !cfg.UpgradeConfig.IsEtnaActivated(timestamp) { - return NewStaticFeeCalculator(cfg, timestamp) + if !config.UpgradeConfig.IsEtnaActivated(timestamp) { + return NewStaticFeeCalculator(config, timestamp) } feeState := state.GetFeeState() gasPrice := gas.CalculatePrice( - cfg.DynamicFeeConfig.MinPrice, + config.DynamicFeeConfig.MinPrice, feeState.Excess, - cfg.DynamicFeeConfig.ExcessConversionConstant, + config.DynamicFeeConfig.ExcessConversionConstant, ) - return fee.NewDynamicCalculator( - cfg.DynamicFeeConfig.Weights, + return txfee.NewDynamicCalculator( + config.DynamicFeeConfig.Weights, gasPrice, ) } // NewStaticFeeCalculator creates a static fee calculator, with the config set // to either the pre-AP3 or post-AP3 config. -func NewStaticFeeCalculator(cfg *config.Config, timestamp time.Time) fee.Calculator { - config := cfg.StaticFeeConfig - if !cfg.UpgradeConfig.IsApricotPhase3Activated(timestamp) { - config.CreateSubnetTxFee = cfg.CreateAssetTxFee - config.CreateBlockchainTxFee = cfg.CreateAssetTxFee +func NewStaticFeeCalculator(config *config.Config, timestamp time.Time) txfee.Calculator { + feeConfig := config.StaticFeeConfig + if !config.UpgradeConfig.IsApricotPhase3Activated(timestamp) { + feeConfig.CreateSubnetTxFee = config.CreateAssetTxFee + feeConfig.CreateBlockchainTxFee = config.CreateAssetTxFee } - return fee.NewStaticCalculator(config) + return txfee.NewStaticCalculator(feeConfig) } diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go index aa8064f2fcd8..4c4c1f5c2b91 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor.go @@ -271,6 +271,7 @@ func (e *ProposalTxExecutor) AdvanceTimeTx(tx *txs.AdvanceTimeTx) error { now := e.Clk.Time() if err := VerifyNewChainTime( + e.Config.ValidatorFeeConfig, newChainTime, now, e.OnCommitState, diff --git a/vms/platformvm/txs/executor/state_changes.go b/vms/platformvm/txs/executor/state_changes.go index a30a422506b9..7c45e0366b89 100644 --- a/vms/platformvm/txs/executor/state_changes.go +++ b/vms/platformvm/txs/executor/state_changes.go @@ -10,9 +10,12 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/reward" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) var ( @@ -30,6 +33,7 @@ var ( // - [newChainTime] <= [nextStakerChangeTime]: so that no staking set changes // are skipped. func VerifyNewChainTime( + config fee.Config, newChainTime time.Time, now time.Time, currentState state.Chain, @@ -57,7 +61,11 @@ func VerifyNewChainTime( // nextStakerChangeTime is calculated last to ensure that the function is // able to be calculated efficiently. - nextStakerChangeTime, err := state.GetNextStakerChangeTime(currentState, newChainTime) + nextStakerChangeTime, err := state.GetNextStakerChangeTime( + config, + currentState, + newChainTime, + ) if err != nil { return fmt.Errorf("could not verify block timestamp: %w", err) } @@ -187,20 +195,63 @@ func AdvanceTimeTo( changed = true } - if backend.Config.UpgradeConfig.IsEtnaActivated(newChainTime) { - previousChainTime := changes.GetTimestamp() - duration := uint64(newChainTime.Sub(previousChainTime) / time.Second) + if !backend.Config.UpgradeConfig.IsEtnaActivated(newChainTime) { + changes.SetTimestamp(newChainTime) + return changed, changes.Apply(parentState) + } - feeState := changes.GetFeeState() - feeState = feeState.AdvanceTime( - backend.Config.DynamicFeeConfig.MaxCapacity, - backend.Config.DynamicFeeConfig.MaxPerSecond, - backend.Config.DynamicFeeConfig.TargetPerSecond, - duration, - ) - changes.SetFeeState(feeState) + // Advance the dynamic fees state + previousChainTime := changes.GetTimestamp() + duration := uint64(newChainTime.Sub(previousChainTime) / time.Second) + + dynamicFeeState := changes.GetFeeState() + dynamicFeeState = dynamicFeeState.AdvanceTime( + backend.Config.DynamicFeeConfig.MaxCapacity, + backend.Config.DynamicFeeConfig.MaxPerSecond, + backend.Config.DynamicFeeConfig.TargetPerSecond, + duration, + ) + changes.SetFeeState(dynamicFeeState) + + validatorFeeState := fee.State{ + Current: gas.Gas(changes.NumActiveSubnetOnlyValidators()), + Excess: changes.GetSoVExcess(), + } + validatorCost := validatorFeeState.CostOf( + backend.Config.ValidatorFeeConfig, + duration, + ) + + accruedFees := changes.GetAccruedFees() + accruedFees, err = math.Add(accruedFees, validatorCost) + if err != nil { + return false, err + } + + sovIterator, err := parentState.GetActiveSubnetOnlyValidatorsIterator() + if err != nil { + return false, err + } + defer sovIterator.Release() + + for sovIterator.Next() { + sov := sovIterator.Value() + if sov.EndAccumulatedFee > accruedFees { + break + } + + sov.EndAccumulatedFee = 0 // Deactivate the validator + if err := changes.PutSubnetOnlyValidator(sov); err != nil { + return false, err + } } + validatorFeeState = validatorFeeState.AdvanceTime( + backend.Config.ValidatorFeeConfig.Target, + duration, + ) + changes.SetSoVExcess(validatorFeeState.Excess) + changes.SetAccruedFees(accruedFees) changes.SetTimestamp(newChainTime) return changed, changes.Apply(parentState) } diff --git a/vms/platformvm/txs/executor/state_changes_test.go b/vms/platformvm/txs/executor/state_changes_test.go index d642e2ed1481..ff9705b75cc9 100644 --- a/vms/platformvm/txs/executor/state_changes_test.go +++ b/vms/platformvm/txs/executor/state_changes_test.go @@ -76,7 +76,10 @@ func TestAdvanceTimeTo_UpdatesFeeState(t *testing.T) { // Ensure the invariant that [nextTime <= nextStakerChangeTime] on // AdvanceTimeTo is maintained. - nextStakerChangeTime, err := state.GetNextStakerChangeTime(s, mockable.MaxTime) + nextStakerChangeTime, err := state.GetNextStakerChangeTime( + s, + mockable.MaxTime, + ) require.NoError(err) require.False(nextTime.After(nextStakerChangeTime)) From 39fba6507d397b41acd521c39b17712105e63472 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 24 Sep 2024 14:55:45 -0400 Subject: [PATCH 068/199] Update wallet example --- .../primary/examples/convert-subnet/main.go | 21 ++++++++++++------- .../primary/examples/create-chain/main.go | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/wallet/subnet/primary/examples/convert-subnet/main.go b/wallet/subnet/primary/examples/convert-subnet/main.go index 5894093638ee..c4233f80cefe 100644 --- a/wallet/subnet/primary/examples/convert-subnet/main.go +++ b/wallet/subnet/primary/examples/convert-subnet/main.go @@ -21,7 +21,7 @@ func main() { key := genesis.EWOQKey uri := "http://localhost:9700" kc := secp256k1fx.NewKeychain(key) - subnetIDStr := "29uVeLPJB1eQJkzRemU8g8wZDw5uJRqpab5U2mX9euieVwiEbL" + subnetIDStr := "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof" weight := units.Schmeckle subnetID, err := ids.FromString(subnetIDStr) @@ -57,13 +57,20 @@ func main() { pWallet := wallet.P() convertSubnetStartTime := time.Now() - addValidatorTx, err := pWallet.IssueAddSubnetValidatorTx(&txs.SubnetValidator{ - Validator: txs.Validator{ - NodeID: nodeID, - Wght: weight, + addValidatorTx, err := pWallet.IssueConvertSubnetTx( + subnetID, + ids.Empty, + nil, + []txs.ConvertSubnetValidator{ + { + NodeID: nodeID, + Weight: weight, + Balance: 30 * units.NanoAvax, // 30s before being deactivated + Signer: nodePoP, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + }, }, - Subnet: subnetID, - }) + ) if err != nil { log.Fatalf("failed to issue add subnet validator transaction: %s\n", err) } diff --git a/wallet/subnet/primary/examples/create-chain/main.go b/wallet/subnet/primary/examples/create-chain/main.go index 9e32490d9aef..61526d9cf38a 100644 --- a/wallet/subnet/primary/examples/create-chain/main.go +++ b/wallet/subnet/primary/examples/create-chain/main.go @@ -22,7 +22,7 @@ func main() { key := genesis.EWOQKey uri := primary.LocalAPIURI kc := secp256k1fx.NewKeychain(key) - subnetIDStr := "29uVeLPJB1eQJkzRemU8g8wZDw5uJRqpab5U2mX9euieVwiEbL" + subnetIDStr := "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof" genesis := &xsgenesis.Genesis{ Timestamp: time.Now().Unix(), Allocations: []xsgenesis.Allocation{ From 3f7a04e9ed75276ea3896369f6ad295c5bdc773a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 24 Sep 2024 14:56:31 -0400 Subject: [PATCH 069/199] Allow blocks that only evict SoVs --- vms/platformvm/txs/executor/state_changes.go | 1 + 1 file changed, 1 insertion(+) diff --git a/vms/platformvm/txs/executor/state_changes.go b/vms/platformvm/txs/executor/state_changes.go index 7c45e0366b89..6dc1abbaaed9 100644 --- a/vms/platformvm/txs/executor/state_changes.go +++ b/vms/platformvm/txs/executor/state_changes.go @@ -244,6 +244,7 @@ func AdvanceTimeTo( if err := changes.PutSubnetOnlyValidator(sov); err != nil { return false, err } + changed = true } validatorFeeState = validatorFeeState.AdvanceTime( From ae9ec6ff4dc486bef60aa8faac35fa5b87dd0f07 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 24 Sep 2024 18:26:06 -0400 Subject: [PATCH 070/199] wip --- vms/platformvm/metrics/tx_metrics.go | 11 ++- vms/platformvm/txs/codec.go | 1 + .../txs/executor/atomic_tx_executor.go | 10 +- .../txs/executor/proposal_tx_executor.go | 4 + .../txs/executor/standard_tx_executor.go | 95 +++++++++++++++++++ vms/platformvm/txs/fee/complexity.go | 44 +++++++++ vms/platformvm/txs/fee/static_calculator.go | 4 + .../txs/register_subnet_validator_tx.go | 63 ++++++++++++ vms/platformvm/txs/visitor.go | 10 +- wallet/chain/p/builder/builder.go | 86 +++++++++++++++++ .../chain/p/builder/builder_with_options.go | 16 ++++ wallet/chain/p/signer/visitor.go | 8 ++ wallet/chain/p/wallet/backend_visitor.go | 4 + 13 files changed, 350 insertions(+), 6 deletions(-) create mode 100644 vms/platformvm/txs/register_subnet_validator_tx.go diff --git a/vms/platformvm/metrics/tx_metrics.go b/vms/platformvm/metrics/tx_metrics.go index fd6c63494f7c..1da84206cf09 100644 --- a/vms/platformvm/metrics/tx_metrics.go +++ b/vms/platformvm/metrics/tx_metrics.go @@ -132,6 +132,13 @@ func (m *txMetrics) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) er return nil } +func (m *txMetrics) BaseTx(*txs.BaseTx) error { + m.numTxs.With(prometheus.Labels{ + txLabel: "base", + }).Inc() + return nil +} + func (m *txMetrics) ConvertSubnetTx(*txs.ConvertSubnetTx) error { m.numTxs.With(prometheus.Labels{ txLabel: "convert_subnet", @@ -139,9 +146,9 @@ func (m *txMetrics) ConvertSubnetTx(*txs.ConvertSubnetTx) error { return nil } -func (m *txMetrics) BaseTx(*txs.BaseTx) error { +func (m *txMetrics) RegisterSubnetValidatorTx(*txs.RegisterSubnetValidatorTx) error { m.numTxs.With(prometheus.Labels{ - txLabel: "base", + txLabel: "register_subnet_validator", }).Inc() return nil } diff --git a/vms/platformvm/txs/codec.go b/vms/platformvm/txs/codec.go index 123da91b8dea..4c3892753555 100644 --- a/vms/platformvm/txs/codec.go +++ b/vms/platformvm/txs/codec.go @@ -123,5 +123,6 @@ func RegisterDurangoTypes(targetCodec linearcodec.Codec) error { func RegisterEtnaTypes(targetCodec linearcodec.Codec) error { return errors.Join( targetCodec.RegisterType(&ConvertSubnetTx{}), + targetCodec.RegisterType(&RegisterSubnetValidatorTx{}), ) } diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index 190c5ec114c9..24bfeda7a8fd 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -66,15 +66,15 @@ func (*AtomicTxExecutor) TransformSubnetTx(*txs.TransformSubnetTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { +func (*AtomicTxExecutor) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { +func (*AtomicTxExecutor) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { +func (*AtomicTxExecutor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { return ErrWrongTxType } @@ -86,6 +86,10 @@ func (*AtomicTxExecutor) ConvertSubnetTx(*txs.ConvertSubnetTx) error { return ErrWrongTxType } +func (*AtomicTxExecutor) RegisterSubnetValidatorTx(*txs.RegisterSubnetValidatorTx) error { + return ErrWrongTxType +} + func (e *AtomicTxExecutor) ImportTx(tx *txs.ImportTx) error { return e.atomicTx(tx) } diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go index 4c4c1f5c2b91..31a84bafecc0 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor.go @@ -103,6 +103,10 @@ func (*ProposalTxExecutor) ConvertSubnetTx(*txs.ConvertSubnetTx) error { return ErrWrongTxType } +func (*ProposalTxExecutor) RegisterSubnetValidatorTx(*txs.RegisterSubnetValidatorTx) error { + return ErrWrongTxType +} + func (e *ProposalTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { // AddValidatorTx is a proposal transaction until the Banff fork // activation. Following the activation, AddValidatorTxs must be issued into diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index f924ec6fee9c..3b57cb8de9d8 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -593,6 +593,101 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { return nil } +// TODO: Implement me +func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetValidatorTx) error { + var ( + currentTimestamp = e.State.GetTimestamp() + upgrades = e.Backend.Config.UpgradeConfig + ) + if !upgrades.IsEtnaActivated(currentTimestamp) { + return errEtnaUpgradeNotActive + } + + if err := e.Tx.SyntacticVerify(e.Ctx); err != nil { + return err + } + + if err := avax.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil { + return err + } + + // Verify the flowcheck + fee, err := e.FeeCalculator.CalculateFee(tx) + if err != nil { + return err + } + fee, err = math.Add(fee, tx.Balance) + if err != nil { + return err + } + + if err := e.Backend.FlowChecker.VerifySpend( + tx, + e.State, + tx.Ins, + tx.Outs, + e.Tx.Creds, + map[ids.ID]uint64{ + e.Ctx.AVAXAssetID: fee, + }, + ); err != nil { + return err + } + + var ( + txID = e.Tx.ID() + startTime = uint64(currentTimestamp.Unix()) + currentFees = e.State.GetAccruedFees() + ) + for i, vdr := range tx.Validators { + vdr := vdr + balanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &vdr.RemainingBalanceOwner) + if err != nil { + return err + } + + sov := state.SubnetOnlyValidator{ + ValidationID: txID.Prefix(uint64(i)), // TODO: The spec says this should be a postfix, not a preifx + SubnetID: tx.Subnet, + NodeID: vdr.NodeID, + PublicKey: bls.PublicKeyToUncompressedBytes(vdr.Signer.Key()), + RemainingBalanceOwner: balanceOwner, + StartTime: startTime, + Weight: vdr.Weight, + MinNonce: 0, + EndAccumulatedFee: 0, // If Balance is 0, this is 0 + } + if vdr.Balance != 0 { + // We are attempting to add an active validator + if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeCapacity { + return errMaxNumActiveValidators + } + + sov.EndAccumulatedFee, err = math.Add(vdr.Balance, currentFees) + if err != nil { + return err + } + + fee, err = math.Add(fee, vdr.Balance) + if err != nil { + return err + } + } + + if err := e.State.PutSubnetOnlyValidator(sov); err != nil { + return err + } + } + + // Consume the UTXOS + avax.Consume(e.State, tx.Ins) + // Produce the UTXOS + avax.Produce(e.State, txID, tx.Outs) + // Set the new Subnet manager in the database + e.State.SetSubnetManager(tx.Subnet, tx.ChainID, tx.Address) + return nil +} + func (e *StandardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { if err := verifyAddPermissionlessValidatorTx( e.Backend, diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 50c5f377e34f..69d0279f8664 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -194,6 +194,16 @@ var ( gas.DBWrite: 2, // manager + weight gas.Compute: 0, } + IntrinsicRegisterSubnetValidatorTxComplexities = gas.Dimensions{ + gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + + wrappers.LongLen + // balance + wrappers.IntLen + // signer typeID + wrappers.IntLen + // owner typeID + wrappers.IntLen, // message length + gas.DBRead: 0, // TODO + gas.DBWrite: 0, // TODO + gas.Compute: 0, + } errUnsupportedOutput = errors.New("unsupported output type") errUnsupportedInput = errors.New("unsupported input type") @@ -427,6 +437,14 @@ func SignerComplexity(s signer.Signer) (gas.Dimensions, error) { } } +// WarpComplexity returns the complexity a warp message adds to a transaction. +func WarpComplexity(message []byte) (gas.Dimensions, error) { + // TODO: Implement me + return gas.Dimensions{ + gas.Bandwidth: uint64(len(message)), + }, nil +} + type complexityVisitor struct { output gas.Dimensions } @@ -677,6 +695,32 @@ func (c *complexityVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { return err } +func (c *complexityVisitor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetValidatorTx) error { + baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) + if err != nil { + return err + } + signerComplexity, err := SignerComplexity(tx.Signer) + if err != nil { + return err + } + ownerComplexity, err := OwnerComplexity(tx.RemainingBalanceOwner) + if err != nil { + return err + } + warpComplexity, err := WarpComplexity(tx.Message) + if err != nil { + return err + } + c.output, err = IntrinsicConvertSubnetTxComplexities.Add( + &baseTxComplexity, + &signerComplexity, + &ownerComplexity, + &warpComplexity, + ) + return err +} + func baseTxComplexity(tx *txs.BaseTx) (gas.Dimensions, error) { outputsComplexity, err := OutputComplexity(tx.Outs...) if err != nil { diff --git a/vms/platformvm/txs/fee/static_calculator.go b/vms/platformvm/txs/fee/static_calculator.go index 888ccba8621c..b2ddd0f91f9d 100644 --- a/vms/platformvm/txs/fee/static_calculator.go +++ b/vms/platformvm/txs/fee/static_calculator.go @@ -51,6 +51,10 @@ func (*staticVisitor) ConvertSubnetTx(*txs.ConvertSubnetTx) error { return ErrUnsupportedTx } +func (*staticVisitor) RegisterSubnetValidatorTx(*txs.RegisterSubnetValidatorTx) error { + return ErrUnsupportedTx +} + func (c *staticVisitor) AddValidatorTx(*txs.AddValidatorTx) error { c.fee = c.config.AddPrimaryNetworkValidatorFee return nil diff --git a/vms/platformvm/txs/register_subnet_validator_tx.go b/vms/platformvm/txs/register_subnet_validator_tx.go new file mode 100644 index 000000000000..b1144b04470b --- /dev/null +++ b/vms/platformvm/txs/register_subnet_validator_tx.go @@ -0,0 +1,63 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txs + +import ( + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/ava-labs/avalanchego/vms/platformvm/fx" + "github.com/ava-labs/avalanchego/vms/platformvm/signer" + "github.com/ava-labs/avalanchego/vms/types" +) + +var _ UnsignedTx = (*RegisterSubnetValidatorTx)(nil) + +type RegisterSubnetValidatorTx struct { + // Metadata, inputs and outputs + BaseTx `serialize:"true"` + // Balance <= sum($AVAX inputs) - sum($AVAX outputs) - TxFee. + Balance uint64 `serialize:"true" json:"balance"` + // [Signer] is the BLS key for this validator. + // Note: We do not enforce that the BLS key is unique across all validators. + // This means that validators can share a key if they so choose. + // However, a NodeID does uniquely map to a BLS key + Signer signer.Signer `serialize:"true" json:"signer"` + // Leftover $AVAX from the Subnet Validator's Balance will be issued to + // this owner after it is removed from the validator set. + RemainingBalanceOwner fx.Owner `serialize:"true" json:"remainingBalanceOwner"` + // AddressedCall with Payload: + // - SubnetID + // - NodeID (must be Ed25519 NodeID) + // - Weight + // - BLS public key + // - Expiry + Message types.JSONByteSlice `serialize:"true" json:"message"` +} + +func (tx *RegisterSubnetValidatorTx) SyntacticVerify(ctx *snow.Context) error { + switch { + case tx == nil: + return ErrNilTx + case tx.SyntacticallyVerified: + // already passed syntactic verification + return nil + } + + if err := tx.BaseTx.SyntacticVerify(ctx); err != nil { + return err + } + if err := verify.All(tx.Signer, tx.RemainingBalanceOwner); err != nil { + return err + } + if tx.Signer.Key() == nil { + return ErrMissingPublicKey + } + + tx.SyntacticallyVerified = true + return nil +} + +func (tx *RegisterSubnetValidatorTx) Visit(visitor Visitor) error { + return visitor.RegisterSubnetValidatorTx(tx) +} diff --git a/vms/platformvm/txs/visitor.go b/vms/platformvm/txs/visitor.go index 142a6115d7af..02627c198f16 100644 --- a/vms/platformvm/txs/visitor.go +++ b/vms/platformvm/txs/visitor.go @@ -5,6 +5,7 @@ package txs // Allow vm to execute custom logic against the underlying transaction types. type Visitor interface { + // Apricot Transactions: AddValidatorTx(*AddValidatorTx) error AddSubnetValidatorTx(*AddSubnetValidatorTx) error AddDelegatorTx(*AddDelegatorTx) error @@ -14,11 +15,18 @@ type Visitor interface { ExportTx(*ExportTx) error AdvanceTimeTx(*AdvanceTimeTx) error RewardValidatorTx(*RewardValidatorTx) error + + // Banff Transactions: RemoveSubnetValidatorTx(*RemoveSubnetValidatorTx) error TransformSubnetTx(*TransformSubnetTx) error AddPermissionlessValidatorTx(*AddPermissionlessValidatorTx) error AddPermissionlessDelegatorTx(*AddPermissionlessDelegatorTx) error + + // Durango Transactions: TransferSubnetOwnershipTx(*TransferSubnetOwnershipTx) error - ConvertSubnetTx(*ConvertSubnetTx) error BaseTx(*BaseTx) error + + // Etna Transactions: + ConvertSubnetTx(*ConvertSubnetTx) error + RegisterSubnetValidatorTx(*RegisterSubnetValidatorTx) error } diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index 5c9c7d3d08d1..5965e4b4934a 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -164,6 +164,22 @@ type Builder interface { options ...common.Option, ) (*txs.ConvertSubnetTx, error) + // RegisterSubnetValidatorTx adds a validator to a Permissionless L1. + // + // - [balance] that the validator should allocate to continuous fees + // - [signer] is the BLS key for this validator + // - [remainingBalanceOwner] specifies the owner to send any of the + // remaining balance after removing the continuous fee + // - [message] is the Warp message that authorizes this validator to be + // added + NewRegisterSubnetValidatorTx( + balance uint64, + signer *signer.ProofOfPossession, + remainingBalanceOwner *secp256k1fx.OutputOwners, + message []byte, + options ...common.Option, + ) (*txs.RegisterSubnetValidatorTx, error) + // NewImportTx creates an import transaction that attempts to consume all // the available UTXOs and import the funds to [to]. // @@ -861,6 +877,76 @@ func (b *builder) NewConvertSubnetTx( return tx, b.initCtx(tx) } +func (b *builder) NewRegisterSubnetValidatorTx( + balance uint64, + signer *signer.ProofOfPossession, + remainingBalanceOwner *secp256k1fx.OutputOwners, + message []byte, + options ...common.Option, +) (*txs.RegisterSubnetValidatorTx, error) { + var ( + toBurn = map[ids.ID]uint64{ + b.context.AVAXAssetID: balance, + } + toStake = map[ids.ID]uint64{} + ) + + ops := common.NewOptions(options) + memo := ops.Memo() + memoComplexity := gas.Dimensions{ + gas.Bandwidth: uint64(len(memo)), + } + signerComplexity, err := fee.SignerComplexity(signer) + if err != nil { + return nil, err + } + ownerComplexity, err := fee.OwnerComplexity(remainingBalanceOwner) + if err != nil { + return nil, err + } + warpComplexity, err := fee.WarpComplexity(message) + if err != nil { + return nil, err + } + complexity, err := fee.IntrinsicRegisterSubnetValidatorTxComplexities.Add( + &memoComplexity, + &signerComplexity, + &ownerComplexity, + &warpComplexity, + ) + if err != nil { + return nil, err + } + + inputs, outputs, _, err := b.spend( + toBurn, + toStake, + 0, + complexity, + nil, + ops, + ) + if err != nil { + return nil, err + } + + utils.Sort(remainingBalanceOwner.Addrs) + tx := &txs.RegisterSubnetValidatorTx{ + BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ + NetworkID: b.context.NetworkID, + BlockchainID: constants.PlatformChainID, + Ins: inputs, + Outs: outputs, + Memo: memo, + }}, + Balance: balance, + Signer: signer, + RemainingBalanceOwner: remainingBalanceOwner, + Message: message, + } + return tx, b.initCtx(tx) +} + func (b *builder) NewImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/chain/p/builder/builder_with_options.go b/wallet/chain/p/builder/builder_with_options.go index 6201dde1fa8f..d13567a42a3c 100644 --- a/wallet/chain/p/builder/builder_with_options.go +++ b/wallet/chain/p/builder/builder_with_options.go @@ -171,6 +171,22 @@ func (b *builderWithOptions) NewConvertSubnetTx( ) } +func (b *builderWithOptions) NewRegisterSubnetValidatorTx( + balance uint64, + signer *signer.ProofOfPossession, + remainingBalanceOwner *secp256k1fx.OutputOwners, + message []byte, + options ...common.Option, +) (*txs.RegisterSubnetValidatorTx, error) { + return b.builder.NewRegisterSubnetValidatorTx( + balance, + signer, + remainingBalanceOwner, + message, + common.UnionOptions(b.options, options)..., + ) +} + func (b *builderWithOptions) NewImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/chain/p/signer/visitor.go b/wallet/chain/p/signer/visitor.go index 38d501c908b0..4a2f1a98f62b 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -169,6 +169,14 @@ func (s *visitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { return sign(s.tx, true, txSigners) } +func (s *visitor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetValidatorTx) error { + txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) + if err != nil { + return err + } + return sign(s.tx, true, txSigners) +} + func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { diff --git a/wallet/chain/p/wallet/backend_visitor.go b/wallet/chain/p/wallet/backend_visitor.go index 8e44ee3b5e2d..3b6bcfaef3aa 100644 --- a/wallet/chain/p/wallet/backend_visitor.go +++ b/wallet/chain/p/wallet/backend_visitor.go @@ -74,6 +74,10 @@ func (b *backendVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { return b.baseTx(&tx.BaseTx) } +func (b *backendVisitor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetValidatorTx) error { + return b.baseTx(&tx.BaseTx) +} + func (b *backendVisitor) BaseTx(tx *txs.BaseTx) error { return b.baseTx(tx) } From 848937089cad30a6bbb554c3c70e5f42dc9a46e7 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 25 Sep 2024 19:32:06 -0400 Subject: [PATCH 071/199] nit --- vms/platformvm/block/executor/verifier.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index 51a09b9cad61..7d13bb038f15 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -542,7 +542,7 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator fee.Calculato accruedFees = diff.GetAccruedFees() potentialCost = validatorFeeState.CostOf( v.txExecutorBackend.Config.ValidatorFeeConfig, - 1, + 1, // 1 second ) ) potentialAccruedFees, err := math.Add(accruedFees, potentialCost) @@ -550,8 +550,6 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator fee.Calculato return nil, nil, nil, err } - // TODO: Remove SoVs that don't have sufficient fee to pay for the next - // second. // Invariant: Proposal transactions do not impact SoV state. sovIterator, err := diff.GetActiveSubnetOnlyValidatorsIterator() if err != nil { @@ -561,7 +559,9 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator fee.Calculato var sovsToDeactivate []state.SubnetOnlyValidator for sovIterator.Next() { sov := sovIterator.Value() - if sov.EndAccumulatedFee > potentialAccruedFees { + // If the validator has exactly the right amount of fee for the next + // second we should not remove them here. + if sov.EndAccumulatedFee >= potentialAccruedFees { break } From 89054328d7f04c16b5bd2d3dcef0cee7f26e9cdb Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 25 Sep 2024 20:10:11 -0400 Subject: [PATCH 072/199] implement execution logic --- .../txs/executor/standard_tx_executor.go | 117 ++++++++++++------ 1 file changed, 78 insertions(+), 39 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 3b57cb8de9d8..96b5c142d687 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -4,6 +4,7 @@ package executor import ( + "bytes" "context" "errors" "fmt" @@ -15,6 +16,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -23,6 +25,9 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" ) var ( @@ -593,7 +598,6 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { return nil } -// TODO: Implement me func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetValidatorTx) error { var ( currentTimestamp = e.State.GetTimestamp() @@ -634,57 +638,92 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return err } - var ( - txID = e.Tx.ID() - startTime = uint64(currentTimestamp.Unix()) - currentFees = e.State.GetAccruedFees() - ) - for i, vdr := range tx.Validators { - vdr := vdr - balanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &vdr.RemainingBalanceOwner) - if err != nil { - return err - } + warpMessage, err := warp.ParseMessage(tx.Message) + if err != nil { + return err + } + if warpMessage.NetworkID != e.Ctx.NetworkID { + return fmt.Errorf("expected networkID %d but got %d", e.Ctx.NetworkID, warpMessage.NetworkID) + } - sov := state.SubnetOnlyValidator{ - ValidationID: txID.Prefix(uint64(i)), // TODO: The spec says this should be a postfix, not a preifx - SubnetID: tx.Subnet, - NodeID: vdr.NodeID, - PublicKey: bls.PublicKeyToUncompressedBytes(vdr.Signer.Key()), - RemainingBalanceOwner: balanceOwner, - StartTime: startTime, - Weight: vdr.Weight, - MinNonce: 0, - EndAccumulatedFee: 0, // If Balance is 0, this is 0 - } - if vdr.Balance != 0 { - // We are attempting to add an active validator - if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeCapacity { - return errMaxNumActiveValidators - } + addressedCall, err := payload.ParseAddressedCall(warpMessage.Payload) + if err != nil { + return err + } - sov.EndAccumulatedFee, err = math.Add(vdr.Balance, currentFees) - if err != nil { - return err - } + msg, err := message.ParseRegisterSubnetValidator(addressedCall.Payload) + if err != nil { + return err + } - fee, err = math.Add(fee, vdr.Balance) - if err != nil { - return err - } + expectedChainID, expectedAddress, err := e.State.GetSubnetManager(msg.SubnetID) + if err != nil { + return err + } + if warpMessage.SourceChainID != expectedChainID { + return fmt.Errorf("expected chainID %s but got %s", expectedChainID, warpMessage.SourceChainID) + } + if !bytes.Equal(addressedCall.SourceAddress, expectedAddress) { + return fmt.Errorf("expected address %s but got %s", expectedAddress, addressedCall.SourceAddress) + } + + currentTimestampUnix := uint64(currentTimestamp.Unix()) + if msg.Expiry <= currentTimestampUnix { + return fmt.Errorf("expected expiry to be after %d but got %d", currentTimestampUnix, msg.Expiry) + } + + validationID := hashing.ComputeHash256Array(addressedCall.Payload) + expiry := state.ExpiryEntry{ + Timestamp: msg.Expiry, + ValidationID: validationID, + } + isDuplicate, err := e.State.HasExpiry(expiry) + if err != nil { + return err + } + if isDuplicate { + return fmt.Errorf("expiry %s already exists", expiry) + } + + balanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &tx.RemainingBalanceOwner) + if err != nil { + return err + } + + sov := state.SubnetOnlyValidator{ + ValidationID: validationID, + SubnetID: msg.SubnetID, + NodeID: msg.NodeID, + PublicKey: bls.PublicKeyToUncompressedBytes(tx.Signer.Key()), + RemainingBalanceOwner: balanceOwner, + StartTime: currentTimestampUnix, + Weight: msg.Weight, + MinNonce: 0, + EndAccumulatedFee: 0, // If Balance is 0, this is 0 + } + if tx.Balance != 0 { + // We are attempting to add an active validator + if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeCapacity { + return errMaxNumActiveValidators } - if err := e.State.PutSubnetOnlyValidator(sov); err != nil { + currentFees := e.State.GetAccruedFees() + sov.EndAccumulatedFee, err = math.Add(tx.Balance, currentFees) + if err != nil { return err } } + if err := e.State.PutSubnetOnlyValidator(sov); err != nil { + return err + } + + txID := e.Tx.ID() + // Consume the UTXOS avax.Consume(e.State, tx.Ins) // Produce the UTXOS avax.Produce(e.State, txID, tx.Outs) - // Set the new Subnet manager in the database - e.State.SetSubnetManager(tx.Subnet, tx.ChainID, tx.Address) return nil } From 2618db27a7f90a2b5d41997f6e6b6e5d58ed88fd Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 26 Sep 2024 11:41:57 -0400 Subject: [PATCH 073/199] remove duplicate public key --- .../txs/executor/standard_tx_executor.go | 13 ++++++++++++- vms/platformvm/txs/fee/complexity.go | 9 ++------- .../txs/register_subnet_validator_tx.go | 15 ++++----------- wallet/chain/p/builder/builder.go | 15 ++++++--------- wallet/chain/p/builder/builder_with_options.go | 5 +++-- 5 files changed, 27 insertions(+), 30 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 96b5c142d687..746f9d136f1e 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -22,6 +22,7 @@ import ( "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" @@ -672,6 +673,14 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return fmt.Errorf("expected expiry to be after %d but got %d", currentTimestampUnix, msg.Expiry) } + pop := signer.ProofOfPossession{ + PublicKey: msg.BLSPublicKey, + ProofOfPossession: tx.ProofOfPossession, + } + if err := pop.Verify(); err != nil { + return err + } + validationID := hashing.ComputeHash256Array(addressedCall.Payload) expiry := state.ExpiryEntry{ Timestamp: msg.Expiry, @@ -694,7 +703,7 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal ValidationID: validationID, SubnetID: msg.SubnetID, NodeID: msg.NodeID, - PublicKey: bls.PublicKeyToUncompressedBytes(tx.Signer.Key()), + PublicKey: bls.PublicKeyToUncompressedBytes(pop.Key()), RemainingBalanceOwner: balanceOwner, StartTime: currentTimestampUnix, Weight: msg.Weight, @@ -724,6 +733,8 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal avax.Consume(e.State, tx.Ins) // Produce the UTXOS avax.Produce(e.State, txID, tx.Outs) + // Prevent this warp message from being replayed + e.State.PutExpiry(expiry) return nil } diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 69d0279f8664..575cb4c6b74d 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -188,11 +188,11 @@ var ( ids.IDLen + // chainID wrappers.IntLen + // address length wrappers.IntLen + // validators length - wrappers.IntLen + // subnetAuth typeID + bls.SignatureLen + // proofOfPossession wrappers.IntLen, // subnetAuthCredential typeID gas.DBRead: 2, // subnet auth + manager lookup gas.DBWrite: 2, // manager + weight - gas.Compute: 0, + gas.Compute: 0, // TODO: Add compute complexity (and include the PoP compute) } IntrinsicRegisterSubnetValidatorTxComplexities = gas.Dimensions{ gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + @@ -700,10 +700,6 @@ func (c *complexityVisitor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVali if err != nil { return err } - signerComplexity, err := SignerComplexity(tx.Signer) - if err != nil { - return err - } ownerComplexity, err := OwnerComplexity(tx.RemainingBalanceOwner) if err != nil { return err @@ -714,7 +710,6 @@ func (c *complexityVisitor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVali } c.output, err = IntrinsicConvertSubnetTxComplexities.Add( &baseTxComplexity, - &signerComplexity, &ownerComplexity, &warpComplexity, ) diff --git a/vms/platformvm/txs/register_subnet_validator_tx.go b/vms/platformvm/txs/register_subnet_validator_tx.go index b1144b04470b..f0d894bba4c8 100644 --- a/vms/platformvm/txs/register_subnet_validator_tx.go +++ b/vms/platformvm/txs/register_subnet_validator_tx.go @@ -5,9 +5,8 @@ package txs import ( "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/vms/platformvm/fx" - "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/types" ) @@ -18,11 +17,8 @@ type RegisterSubnetValidatorTx struct { BaseTx `serialize:"true"` // Balance <= sum($AVAX inputs) - sum($AVAX outputs) - TxFee. Balance uint64 `serialize:"true" json:"balance"` - // [Signer] is the BLS key for this validator. - // Note: We do not enforce that the BLS key is unique across all validators. - // This means that validators can share a key if they so choose. - // However, a NodeID does uniquely map to a BLS key - Signer signer.Signer `serialize:"true" json:"signer"` + // ProofOfPossession of the BLS key that is included in the Message. + ProofOfPossession [bls.SignatureLen]byte `serialize:"true" json:"proofOfPossession"` // Leftover $AVAX from the Subnet Validator's Balance will be issued to // this owner after it is removed from the validator set. RemainingBalanceOwner fx.Owner `serialize:"true" json:"remainingBalanceOwner"` @@ -47,12 +43,9 @@ func (tx *RegisterSubnetValidatorTx) SyntacticVerify(ctx *snow.Context) error { if err := tx.BaseTx.SyntacticVerify(ctx); err != nil { return err } - if err := verify.All(tx.Signer, tx.RemainingBalanceOwner); err != nil { + if err := tx.RemainingBalanceOwner.Verify(); err != nil { return err } - if tx.Signer.Key() == nil { - return ErrMissingPublicKey - } tx.SyntacticallyVerified = true return nil diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index 5965e4b4934a..b15a567d3690 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -167,14 +168,15 @@ type Builder interface { // RegisterSubnetValidatorTx adds a validator to a Permissionless L1. // // - [balance] that the validator should allocate to continuous fees - // - [signer] is the BLS key for this validator + // - [proofOfPossession] is the BLS PoP for the key included in the Warp + // message // - [remainingBalanceOwner] specifies the owner to send any of the // remaining balance after removing the continuous fee // - [message] is the Warp message that authorizes this validator to be // added NewRegisterSubnetValidatorTx( balance uint64, - signer *signer.ProofOfPossession, + proofOfPossession [bls.SignatureLen]byte, remainingBalanceOwner *secp256k1fx.OutputOwners, message []byte, options ...common.Option, @@ -879,7 +881,7 @@ func (b *builder) NewConvertSubnetTx( func (b *builder) NewRegisterSubnetValidatorTx( balance uint64, - signer *signer.ProofOfPossession, + proofOfPossession [bls.SignatureLen]byte, remainingBalanceOwner *secp256k1fx.OutputOwners, message []byte, options ...common.Option, @@ -896,10 +898,6 @@ func (b *builder) NewRegisterSubnetValidatorTx( memoComplexity := gas.Dimensions{ gas.Bandwidth: uint64(len(memo)), } - signerComplexity, err := fee.SignerComplexity(signer) - if err != nil { - return nil, err - } ownerComplexity, err := fee.OwnerComplexity(remainingBalanceOwner) if err != nil { return nil, err @@ -910,7 +908,6 @@ func (b *builder) NewRegisterSubnetValidatorTx( } complexity, err := fee.IntrinsicRegisterSubnetValidatorTxComplexities.Add( &memoComplexity, - &signerComplexity, &ownerComplexity, &warpComplexity, ) @@ -940,7 +937,7 @@ func (b *builder) NewRegisterSubnetValidatorTx( Memo: memo, }}, Balance: balance, - Signer: signer, + ProofOfPossession: proofOfPossession, RemainingBalanceOwner: remainingBalanceOwner, Message: message, } diff --git a/wallet/chain/p/builder/builder_with_options.go b/wallet/chain/p/builder/builder_with_options.go index d13567a42a3c..fd83ce62f0ab 100644 --- a/wallet/chain/p/builder/builder_with_options.go +++ b/wallet/chain/p/builder/builder_with_options.go @@ -7,6 +7,7 @@ import ( "time" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/platformvm/txs" @@ -173,14 +174,14 @@ func (b *builderWithOptions) NewConvertSubnetTx( func (b *builderWithOptions) NewRegisterSubnetValidatorTx( balance uint64, - signer *signer.ProofOfPossession, + proofOfPossession [bls.SignatureLen]byte, remainingBalanceOwner *secp256k1fx.OutputOwners, message []byte, options ...common.Option, ) (*txs.RegisterSubnetValidatorTx, error) { return b.builder.NewRegisterSubnetValidatorTx( balance, - signer, + proofOfPossession, remainingBalanceOwner, message, common.UnionOptions(b.options, options)..., From 322642b7354f2b355a3b07e1b94d30885e5c76ee Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 26 Sep 2024 12:26:05 -0400 Subject: [PATCH 074/199] finish e2e RegisterSubnetValidatorTx --- upgrade/upgrade.go | 2 +- .../txs/executor/standard_tx_executor.go | 18 +++ wallet/chain/p/wallet/wallet.go | 33 ++++++ wallet/chain/p/wallet/with_options.go | 17 +++ .../primary/examples/convert-subnet/main.go | 15 ++- .../register-subnet-validator/main.go | 111 ++++++++++++++++++ 6 files changed, 189 insertions(+), 7 deletions(-) create mode 100644 wallet/subnet/primary/examples/register-subnet-validator/main.go diff --git a/upgrade/upgrade.go b/upgrade/upgrade.go index 21e404ffb3f4..405adeffb5b7 100644 --- a/upgrade/upgrade.go +++ b/upgrade/upgrade.go @@ -72,7 +72,7 @@ var ( DurangoTime: InitiallyActiveTime, // Etna is left unactivated by default on local networks. It can be configured to // activate by overriding the activation time in the upgrade file. - EtnaTime: UnscheduledActivationTime, + EtnaTime: InitiallyActiveTime, } ErrInvalidUpgradeTimes = errors.New("invalid upgrade configuration") diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 746f9d136f1e..bc7ff7d88860 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -31,6 +31,14 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" ) +const ( + second = 1 + minute = 60 * second + hour = 60 * minute + day = 24 * hour + RegisterSubnetValidatorTxExpiryWindow = day +) + var ( _ txs.Visitor = (*StandardTxExecutor)(nil) @@ -672,6 +680,16 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal if msg.Expiry <= currentTimestampUnix { return fmt.Errorf("expected expiry to be after %d but got %d", currentTimestampUnix, msg.Expiry) } + maxAllowedExpiry, err := math.Add(currentTimestampUnix, RegisterSubnetValidatorTxExpiryWindow) + if err != nil { + // This should never happen, as it would imply that either + // currentTimestampUnix or RegisterSubnetValidatorTxExpiryWindow is + // significantly larger than expected. + return err + } + if msg.Expiry > maxAllowedExpiry { + return fmt.Errorf("expected expiry to be before %d but got %d", maxAllowedExpiry, msg.Expiry) + } pop := signer.ProofOfPossession{ PublicKey: msg.BLSPublicKey, diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index 2f1d788fd7d5..7811ad3b6f8b 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -7,6 +7,7 @@ import ( "time" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -150,6 +151,24 @@ type Wallet interface { options ...common.Option, ) (*txs.Tx, error) + // IssueRegisterSubnetValidatorTx creates, signs, and issues a transaction + // that adds a validator to a Permissionless L1. + // + // - [balance] that the validator should allocate to continuous fees + // - [proofOfPossession] is the BLS PoP for the key included in the Warp + // message + // - [remainingBalanceOwner] specifies the owner to send any of the + // remaining balance after removing the continuous fee + // - [message] is the Warp message that authorizes this validator to be + // added + IssueRegisterSubnetValidatorTx( + balance uint64, + proofOfPossession [bls.SignatureLen]byte, + remainingBalanceOwner *secp256k1fx.OutputOwners, + message []byte, + options ...common.Option, + ) (*txs.Tx, error) + // IssueImportTx creates, signs, and issues an import transaction that // attempts to consume all the available UTXOs and import the funds to [to]. // @@ -404,6 +423,20 @@ func (w *wallet) IssueConvertSubnetTx( return w.IssueUnsignedTx(utx, options...) } +func (w *wallet) IssueRegisterSubnetValidatorTx( + balance uint64, + proofOfPossession [bls.SignatureLen]byte, + remainingBalanceOwner *secp256k1fx.OutputOwners, + message []byte, + options ...common.Option, +) (*txs.Tx, error) { + utx, err := w.builder.NewRegisterSubnetValidatorTx(balance, proofOfPossession, remainingBalanceOwner, message, options...) + if err != nil { + return nil, err + } + return w.IssueUnsignedTx(utx, options...) +} + func (w *wallet) IssueImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/chain/p/wallet/with_options.go b/wallet/chain/p/wallet/with_options.go index 3027b46d5428..ab456d7d469e 100644 --- a/wallet/chain/p/wallet/with_options.go +++ b/wallet/chain/p/wallet/with_options.go @@ -7,6 +7,7 @@ import ( "time" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -159,6 +160,22 @@ func (w *withOptions) IssueConvertSubnetTx( ) } +func (w *withOptions) IssueRegisterSubnetValidatorTx( + balance uint64, + proofOfPossession [bls.SignatureLen]byte, + remainingBalanceOwner *secp256k1fx.OutputOwners, + message []byte, + options ...common.Option, +) (*txs.Tx, error) { + return w.wallet.IssueRegisterSubnetValidatorTx( + balance, + proofOfPossession, + remainingBalanceOwner, + message, + common.UnionOptions(w.options, options)..., + ) +} + func (w *withOptions) IssueImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/subnet/primary/examples/convert-subnet/main.go b/wallet/subnet/primary/examples/convert-subnet/main.go index c4233f80cefe..14b6f6b588c6 100644 --- a/wallet/subnet/primary/examples/convert-subnet/main.go +++ b/wallet/subnet/primary/examples/convert-subnet/main.go @@ -5,6 +5,7 @@ package main import ( "context" + "encoding/hex" "log" "time" @@ -21,12 +22,14 @@ func main() { key := genesis.EWOQKey uri := "http://localhost:9700" kc := secp256k1fx.NewKeychain(key) - subnetIDStr := "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof" + subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") + chainID := ids.FromStringOrPanic("2f23iBApzAwJy1LRrgGZ3pGGK4S6UrakHMejZsQiwKy4rKfnzx") + addressHex := "" weight := units.Schmeckle - subnetID, err := ids.FromString(subnetIDStr) + address, err := hex.DecodeString(addressHex) if err != nil { - log.Fatalf("failed to parse subnet ID: %s\n", err) + log.Fatalf("failed to decode address %q: %s\n", addressHex, err) } ctx := context.Background() @@ -59,13 +62,13 @@ func main() { convertSubnetStartTime := time.Now() addValidatorTx, err := pWallet.IssueConvertSubnetTx( subnetID, - ids.Empty, - nil, + chainID, + address, []txs.ConvertSubnetValidator{ { NodeID: nodeID, Weight: weight, - Balance: 30 * units.NanoAvax, // 30s before being deactivated + Balance: units.Avax, Signer: nodePoP, RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, }, diff --git a/wallet/subnet/primary/examples/register-subnet-validator/main.go b/wallet/subnet/primary/examples/register-subnet-validator/main.go new file mode 100644 index 000000000000..eb9504b7238a --- /dev/null +++ b/wallet/subnet/primary/examples/register-subnet-validator/main.go @@ -0,0 +1,111 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "encoding/hex" + "log" + "time" + + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/genesis" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" +) + +func main() { + key := genesis.EWOQKey + uri := "http://localhost:9710" + kc := secp256k1fx.NewKeychain(key) + subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") + chainID := ids.FromStringOrPanic("2f23iBApzAwJy1LRrgGZ3pGGK4S6UrakHMejZsQiwKy4rKfnzx") + addressHex := "" + weight := units.Schmeckle + + address, err := hex.DecodeString(addressHex) + if err != nil { + log.Fatalf("failed to decode address %q: %s\n", addressHex, err) + } + + ctx := context.Background() + infoClient := info.NewClient(uri) + + nodeInfoStartTime := time.Now() + nodeID, nodePoP, err := infoClient.GetNodeID(ctx) + if err != nil { + log.Fatalf("failed to fetch node IDs: %s\n", err) + } + log.Printf("fetched node ID %s in %s\n", nodeID, time.Since(nodeInfoStartTime)) + + // MakeWallet fetches the available UTXOs owned by [kc] on the network that + // [uri] is hosting and registers [subnetID]. + walletSyncStartTime := time.Now() + wallet, err := primary.MakeWallet(ctx, &primary.WalletConfig{ + URI: uri, + AVAXKeychain: kc, + EthKeychain: kc, + }) + if err != nil { + log.Fatalf("failed to initialize wallet: %s\n", err) + } + log.Printf("synced wallet in %s\n", time.Since(walletSyncStartTime)) + + // Get the P-chain wallet + pWallet := wallet.P() + context := pWallet.Builder().Context() + + addressedCallPayload, err := message.NewRegisterSubnetValidator( + subnetID, + nodeID, + weight, + nodePoP.PublicKey, + uint64(time.Now().Add(5*time.Minute).Unix()), + ) + if err != nil { + log.Fatalf("failed to create RegisterSubnetValidator message: %s\n", err) + } + + addressedCall, err := payload.NewAddressedCall( + address, + addressedCallPayload.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create AddressedCall message: %s\n", err) + } + + unsignedWarp, err := warp.NewUnsignedMessage( + context.NetworkID, + chainID, + addressedCall.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create unsigned Warp message: %s\n", err) + } + + warp, err := warp.NewMessage( + unsignedWarp, + &warp.BitSetSignature{}, + ) + if err != nil { + log.Fatalf("failed to create Warp message: %s\n", err) + } + + convertSubnetStartTime := time.Now() + addValidatorTx, err := pWallet.IssueRegisterSubnetValidatorTx( + units.Avax, + nodePoP.ProofOfPossession, + &secp256k1fx.OutputOwners{}, + warp.Bytes(), + ) + if err != nil { + log.Fatalf("failed to issue add subnet validator transaction: %s\n", err) + } + log.Printf("added new subnet validator %s to %s with %s in %s\n", nodeID, subnetID, addValidatorTx.ID(), time.Since(convertSubnetStartTime)) +} From d68d93532cf4e4d1e0e8d79c472fbe563fa55478 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 26 Sep 2024 12:36:58 -0400 Subject: [PATCH 075/199] improve error + logs --- vms/platformvm/txs/executor/standard_tx_executor.go | 2 +- .../primary/examples/register-subnet-validator/main.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index bc7ff7d88860..b2a0895ed263 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -709,7 +709,7 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return err } if isDuplicate { - return fmt.Errorf("expiry %s already exists", expiry) + return fmt.Errorf("expiry for %s already exists", validationID) } balanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &tx.RemainingBalanceOwner) diff --git a/wallet/subnet/primary/examples/register-subnet-validator/main.go b/wallet/subnet/primary/examples/register-subnet-validator/main.go index eb9504b7238a..2eb92b164ff0 100644 --- a/wallet/subnet/primary/examples/register-subnet-validator/main.go +++ b/wallet/subnet/primary/examples/register-subnet-validator/main.go @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" @@ -107,5 +108,7 @@ func main() { if err != nil { log.Fatalf("failed to issue add subnet validator transaction: %s\n", err) } - log.Printf("added new subnet validator %s to %s with %s in %s\n", nodeID, subnetID, addValidatorTx.ID(), time.Since(convertSubnetStartTime)) + + validationID := hashing.ComputeHash256Array(addressedCallPayload.Bytes()) + log.Printf("added new subnet validator %s to %s with %s as %s in %s\n", nodeID, subnetID, addValidatorTx.ID(), validationID, time.Since(convertSubnetStartTime)) } From dd41b730f4c589018a9147c1cb6c5d169d66f686 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 26 Sep 2024 12:39:25 -0400 Subject: [PATCH 076/199] improve doc --- vms/platformvm/txs/register_subnet_validator_tx.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/vms/platformvm/txs/register_subnet_validator_tx.go b/vms/platformvm/txs/register_subnet_validator_tx.go index f0d894bba4c8..bb1d3e2d4ecc 100644 --- a/vms/platformvm/txs/register_subnet_validator_tx.go +++ b/vms/platformvm/txs/register_subnet_validator_tx.go @@ -22,12 +22,8 @@ type RegisterSubnetValidatorTx struct { // Leftover $AVAX from the Subnet Validator's Balance will be issued to // this owner after it is removed from the validator set. RemainingBalanceOwner fx.Owner `serialize:"true" json:"remainingBalanceOwner"` - // AddressedCall with Payload: - // - SubnetID - // - NodeID (must be Ed25519 NodeID) - // - Weight - // - BLS public key - // - Expiry + // Message is expected to be a signed Warp message containing an + // AddressedCall payload with the RegisterSubnetValidator message. Message types.JSONByteSlice `serialize:"true" json:"message"` } From ef71d413e581b0eafee8d5ff3801442a7bb81398 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 26 Sep 2024 15:10:03 -0400 Subject: [PATCH 077/199] ACP-77: Implement SetSubnetValidatorWeightTx --- vms/platformvm/metrics/tx_metrics.go | 7 ++ vms/platformvm/txs/codec.go | 1 + .../txs/executor/atomic_tx_executor.go | 4 + .../txs/executor/proposal_tx_executor.go | 4 + .../txs/executor/standard_tx_executor.go | 107 +++++++++++++++++- vms/platformvm/txs/fee/complexity.go | 23 ++++ vms/platformvm/txs/fee/static_calculator.go | 4 + .../txs/set_subnet_validator_weight_tx.go | 40 +++++++ vms/platformvm/txs/visitor.go | 1 + wallet/chain/p/builder/builder.go | 60 ++++++++++ .../chain/p/builder/builder_with_options.go | 10 ++ wallet/chain/p/signer/visitor.go | 8 ++ wallet/chain/p/wallet/backend_visitor.go | 4 + wallet/chain/p/wallet/wallet.go | 21 ++++ wallet/chain/p/wallet/with_options.go | 10 ++ .../primary/examples/convert-subnet/main.go | 4 +- .../primary/examples/create-chain/main.go | 2 +- .../register-subnet-validator/main.go | 8 +- .../set-subnet-validator-weight/main.go | 96 ++++++++++++++++ 19 files changed, 401 insertions(+), 13 deletions(-) create mode 100644 vms/platformvm/txs/set_subnet_validator_weight_tx.go create mode 100644 wallet/subnet/primary/examples/set-subnet-validator-weight/main.go diff --git a/vms/platformvm/metrics/tx_metrics.go b/vms/platformvm/metrics/tx_metrics.go index 1da84206cf09..c194f603f97b 100644 --- a/vms/platformvm/metrics/tx_metrics.go +++ b/vms/platformvm/metrics/tx_metrics.go @@ -152,3 +152,10 @@ func (m *txMetrics) RegisterSubnetValidatorTx(*txs.RegisterSubnetValidatorTx) er }).Inc() return nil } + +func (m *txMetrics) SetSubnetValidatorWeightTx(*txs.SetSubnetValidatorWeightTx) error { + m.numTxs.With(prometheus.Labels{ + txLabel: "set_subnet_validator_weight", + }).Inc() + return nil +} diff --git a/vms/platformvm/txs/codec.go b/vms/platformvm/txs/codec.go index 4c3892753555..51e56e380e99 100644 --- a/vms/platformvm/txs/codec.go +++ b/vms/platformvm/txs/codec.go @@ -124,5 +124,6 @@ func RegisterEtnaTypes(targetCodec linearcodec.Codec) error { return errors.Join( targetCodec.RegisterType(&ConvertSubnetTx{}), targetCodec.RegisterType(&RegisterSubnetValidatorTx{}), + targetCodec.RegisterType(&SetSubnetValidatorWeightTx{}), ) } diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index 24bfeda7a8fd..7327766b5360 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -90,6 +90,10 @@ func (*AtomicTxExecutor) RegisterSubnetValidatorTx(*txs.RegisterSubnetValidatorT return ErrWrongTxType } +func (*AtomicTxExecutor) SetSubnetValidatorWeightTx(*txs.SetSubnetValidatorWeightTx) error { + return ErrWrongTxType +} + func (e *AtomicTxExecutor) ImportTx(tx *txs.ImportTx) error { return e.atomicTx(tx) } diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go index 31a84bafecc0..93b2f5aff506 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor.go @@ -107,6 +107,10 @@ func (*ProposalTxExecutor) RegisterSubnetValidatorTx(*txs.RegisterSubnetValidato return ErrWrongTxType } +func (*ProposalTxExecutor) SetSubnetValidatorWeightTx(*txs.SetSubnetValidatorWeightTx) error { + return ErrWrongTxType +} + func (e *ProposalTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { // AddValidatorTx is a proposal transaction until the Banff fork // activation. Following the activation, AddValidatorTxs must be issued into diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index b2a0895ed263..87849ae19fbb 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -8,6 +8,7 @@ import ( "context" "errors" "fmt" + "math" "time" "go.uber.org/zap" @@ -17,7 +18,6 @@ import ( "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/hashing" - "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/gas" @@ -29,6 +29,8 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + + safemath "github.com/ava-labs/avalanchego/utils/math" ) const ( @@ -570,12 +572,12 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { return errMaxNumActiveValidators } - sov.EndAccumulatedFee, err = math.Add(vdr.Balance, currentFees) + sov.EndAccumulatedFee, err = safemath.Add(vdr.Balance, currentFees) if err != nil { return err } - fee, err = math.Add(fee, vdr.Balance) + fee, err = safemath.Add(fee, vdr.Balance) if err != nil { return err } @@ -629,7 +631,7 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal if err != nil { return err } - fee, err = math.Add(fee, tx.Balance) + fee, err = safemath.Add(fee, tx.Balance) if err != nil { return err } @@ -680,7 +682,7 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal if msg.Expiry <= currentTimestampUnix { return fmt.Errorf("expected expiry to be after %d but got %d", currentTimestampUnix, msg.Expiry) } - maxAllowedExpiry, err := math.Add(currentTimestampUnix, RegisterSubnetValidatorTxExpiryWindow) + maxAllowedExpiry, err := safemath.Add(currentTimestampUnix, RegisterSubnetValidatorTxExpiryWindow) if err != nil { // This should never happen, as it would imply that either // currentTimestampUnix or RegisterSubnetValidatorTxExpiryWindow is @@ -735,7 +737,7 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal } currentFees := e.State.GetAccruedFees() - sov.EndAccumulatedFee, err = math.Add(tx.Balance, currentFees) + sov.EndAccumulatedFee, err = safemath.Add(tx.Balance, currentFees) if err != nil { return err } @@ -756,6 +758,99 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return nil } +func (e *StandardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidatorWeightTx) error { + var ( + currentTimestamp = e.State.GetTimestamp() + upgrades = e.Backend.Config.UpgradeConfig + ) + if !upgrades.IsEtnaActivated(currentTimestamp) { + return errEtnaUpgradeNotActive + } + + if err := e.Tx.SyntacticVerify(e.Ctx); err != nil { + return err + } + + if err := avax.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil { + return err + } + + // Verify the flowcheck + fee, err := e.FeeCalculator.CalculateFee(tx) + if err != nil { + return err + } + + if err := e.Backend.FlowChecker.VerifySpend( + tx, + e.State, + tx.Ins, + tx.Outs, + e.Tx.Creds, + map[ids.ID]uint64{ + e.Ctx.AVAXAssetID: fee, + }, + ); err != nil { + return err + } + + warpMessage, err := warp.ParseMessage(tx.Message) + if err != nil { + return err + } + if warpMessage.NetworkID != e.Ctx.NetworkID { + return fmt.Errorf("expected networkID %d but got %d", e.Ctx.NetworkID, warpMessage.NetworkID) + } + + addressedCall, err := payload.ParseAddressedCall(warpMessage.Payload) + if err != nil { + return err + } + + msg, err := message.ParseSetSubnetValidatorWeight(addressedCall.Payload) + if err != nil { + return err + } + if msg.Nonce == math.MaxUint64 && msg.Weight != 0 { + return fmt.Errorf("setting nonce to %d can only be done when removing the validator", msg.Nonce) + } + + sov, err := e.State.GetSubnetOnlyValidator(msg.ValidationID) + if err != nil { + return err + } + if msg.Nonce < sov.MinNonce { + return fmt.Errorf("expected nonce to be at least %d but got %d", sov.MinNonce, msg.Nonce) + } + + expectedChainID, expectedAddress, err := e.State.GetSubnetManager(sov.SubnetID) + if err != nil { + return err + } + if warpMessage.SourceChainID != expectedChainID { + return fmt.Errorf("expected chainID %s but got %s", expectedChainID, warpMessage.SourceChainID) + } + if !bytes.Equal(addressedCall.SourceAddress, expectedAddress) { + return fmt.Errorf("expected address %s but got %s", expectedAddress, addressedCall.SourceAddress) + } + + // If the nonce calculation overflows, weight must be 0, so the validator is + // being removed anyways. + sov.MinNonce = msg.Nonce + 1 + sov.Weight = msg.Weight + if err := e.State.PutSubnetOnlyValidator(sov); err != nil { + return err + } + + txID := e.Tx.ID() + + // Consume the UTXOS + avax.Consume(e.State, tx.Ins) + // Produce the UTXOS + avax.Produce(e.State, txID, tx.Outs) + return nil +} + func (e *StandardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { if err := verifyAddPermissionlessValidatorTx( e.Backend, diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 575cb4c6b74d..a5a746cdbcaf 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -204,6 +204,13 @@ var ( gas.DBWrite: 0, // TODO gas.Compute: 0, } + IntrinsicSetSubnetValidatorWeightTxComplexities = gas.Dimensions{ + gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + + wrappers.IntLen, // message length + gas.DBRead: 0, // TODO + gas.DBWrite: 0, // TODO + gas.Compute: 0, + } errUnsupportedOutput = errors.New("unsupported output type") errUnsupportedInput = errors.New("unsupported input type") @@ -716,6 +723,22 @@ func (c *complexityVisitor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVali return err } +func (c *complexityVisitor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidatorWeightTx) error { + baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) + if err != nil { + return err + } + warpComplexity, err := WarpComplexity(tx.Message) + if err != nil { + return err + } + c.output, err = IntrinsicSetSubnetValidatorWeightTxComplexities.Add( + &baseTxComplexity, + &warpComplexity, + ) + return err +} + func baseTxComplexity(tx *txs.BaseTx) (gas.Dimensions, error) { outputsComplexity, err := OutputComplexity(tx.Outs...) if err != nil { diff --git a/vms/platformvm/txs/fee/static_calculator.go b/vms/platformvm/txs/fee/static_calculator.go index b2ddd0f91f9d..8e58827dcb35 100644 --- a/vms/platformvm/txs/fee/static_calculator.go +++ b/vms/platformvm/txs/fee/static_calculator.go @@ -55,6 +55,10 @@ func (*staticVisitor) RegisterSubnetValidatorTx(*txs.RegisterSubnetValidatorTx) return ErrUnsupportedTx } +func (*staticVisitor) SetSubnetValidatorWeightTx(*txs.SetSubnetValidatorWeightTx) error { + return ErrUnsupportedTx +} + func (c *staticVisitor) AddValidatorTx(*txs.AddValidatorTx) error { c.fee = c.config.AddPrimaryNetworkValidatorFee return nil diff --git a/vms/platformvm/txs/set_subnet_validator_weight_tx.go b/vms/platformvm/txs/set_subnet_validator_weight_tx.go new file mode 100644 index 000000000000..519e0bd3bd97 --- /dev/null +++ b/vms/platformvm/txs/set_subnet_validator_weight_tx.go @@ -0,0 +1,40 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txs + +import ( + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/vms/types" +) + +var _ UnsignedTx = (*SetSubnetValidatorWeightTx)(nil) + +type SetSubnetValidatorWeightTx struct { + // Metadata, inputs and outputs + BaseTx `serialize:"true"` + // Message is expected to be a signed Warp message containing an + // AddressedCall payload with the SetSubnetValidatorWeight message. + Message types.JSONByteSlice `serialize:"true" json:"message"` +} + +func (tx *SetSubnetValidatorWeightTx) SyntacticVerify(ctx *snow.Context) error { + switch { + case tx == nil: + return ErrNilTx + case tx.SyntacticallyVerified: + // already passed syntactic verification + return nil + } + + if err := tx.BaseTx.SyntacticVerify(ctx); err != nil { + return err + } + + tx.SyntacticallyVerified = true + return nil +} + +func (tx *SetSubnetValidatorWeightTx) Visit(visitor Visitor) error { + return visitor.SetSubnetValidatorWeightTx(tx) +} diff --git a/vms/platformvm/txs/visitor.go b/vms/platformvm/txs/visitor.go index 02627c198f16..6aa766e1e3ea 100644 --- a/vms/platformvm/txs/visitor.go +++ b/vms/platformvm/txs/visitor.go @@ -29,4 +29,5 @@ type Visitor interface { // Etna Transactions: ConvertSubnetTx(*ConvertSubnetTx) error RegisterSubnetValidatorTx(*RegisterSubnetValidatorTx) error + SetSubnetValidatorWeightTx(*SetSubnetValidatorWeightTx) error } diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index b15a567d3690..e707366416a0 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -182,6 +182,16 @@ type Builder interface { options ...common.Option, ) (*txs.RegisterSubnetValidatorTx, error) + // NewSetSubnetValidatorWeightTx sets the weight of a validator on a + // Permissionless L1. + // + // - [message] is the Warp message that authorizes this validator's weight + // to be changed + NewSetSubnetValidatorWeightTx( + message []byte, + options ...common.Option, + ) (*txs.SetSubnetValidatorWeightTx, error) + // NewImportTx creates an import transaction that attempts to consume all // the available UTXOs and import the funds to [to]. // @@ -944,6 +954,56 @@ func (b *builder) NewRegisterSubnetValidatorTx( return tx, b.initCtx(tx) } +func (b *builder) NewSetSubnetValidatorWeightTx( + message []byte, + options ...common.Option, +) (*txs.SetSubnetValidatorWeightTx, error) { + var ( + toBurn = map[ids.ID]uint64{} + toStake = map[ids.ID]uint64{} + ops = common.NewOptions(options) + memo = ops.Memo() + memoComplexity = gas.Dimensions{ + gas.Bandwidth: uint64(len(memo)), + } + ) + warpComplexity, err := fee.WarpComplexity(message) + if err != nil { + return nil, err + } + complexity, err := fee.IntrinsicRegisterSubnetValidatorTxComplexities.Add( + &memoComplexity, + &warpComplexity, + ) + if err != nil { + return nil, err + } + + inputs, outputs, _, err := b.spend( + toBurn, + toStake, + 0, + complexity, + nil, + ops, + ) + if err != nil { + return nil, err + } + + tx := &txs.SetSubnetValidatorWeightTx{ + BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ + NetworkID: b.context.NetworkID, + BlockchainID: constants.PlatformChainID, + Ins: inputs, + Outs: outputs, + Memo: memo, + }}, + Message: message, + } + return tx, b.initCtx(tx) +} + func (b *builder) NewImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/chain/p/builder/builder_with_options.go b/wallet/chain/p/builder/builder_with_options.go index fd83ce62f0ab..d3a55de058d9 100644 --- a/wallet/chain/p/builder/builder_with_options.go +++ b/wallet/chain/p/builder/builder_with_options.go @@ -188,6 +188,16 @@ func (b *builderWithOptions) NewRegisterSubnetValidatorTx( ) } +func (b *builderWithOptions) NewSetSubnetValidatorWeightTx( + message []byte, + options ...common.Option, +) (*txs.SetSubnetValidatorWeightTx, error) { + return b.builder.NewSetSubnetValidatorWeightTx( + message, + common.UnionOptions(b.options, options)..., + ) +} + func (b *builderWithOptions) NewImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/chain/p/signer/visitor.go b/wallet/chain/p/signer/visitor.go index 4a2f1a98f62b..8a6f1bf0e782 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -177,6 +177,14 @@ func (s *visitor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetValidatorTx) e return sign(s.tx, true, txSigners) } +func (s *visitor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidatorWeightTx) error { + txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) + if err != nil { + return err + } + return sign(s.tx, true, txSigners) +} + func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { diff --git a/wallet/chain/p/wallet/backend_visitor.go b/wallet/chain/p/wallet/backend_visitor.go index 3b6bcfaef3aa..db0b544d4f88 100644 --- a/wallet/chain/p/wallet/backend_visitor.go +++ b/wallet/chain/p/wallet/backend_visitor.go @@ -78,6 +78,10 @@ func (b *backendVisitor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetValidat return b.baseTx(&tx.BaseTx) } +func (b *backendVisitor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidatorWeightTx) error { + return b.baseTx(&tx.BaseTx) +} + func (b *backendVisitor) BaseTx(tx *txs.BaseTx) error { return b.baseTx(tx) } diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index 7811ad3b6f8b..bf0a614a0eaf 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -169,6 +169,16 @@ type Wallet interface { options ...common.Option, ) (*txs.Tx, error) + // IssueSetSubnetValidatorWeightTx creates, signs, and issues a transaction + // that sets the weight of a validator on a Permissionless L1. + // + // - [message] is the Warp message that authorizes this validator's weight + // to be changed + IssueSetSubnetValidatorWeightTx( + message []byte, + options ...common.Option, + ) (*txs.Tx, error) + // IssueImportTx creates, signs, and issues an import transaction that // attempts to consume all the available UTXOs and import the funds to [to]. // @@ -437,6 +447,17 @@ func (w *wallet) IssueRegisterSubnetValidatorTx( return w.IssueUnsignedTx(utx, options...) } +func (w *wallet) IssueSetSubnetValidatorWeightTx( + message []byte, + options ...common.Option, +) (*txs.Tx, error) { + utx, err := w.builder.NewSetSubnetValidatorWeightTx(message, options...) + if err != nil { + return nil, err + } + return w.IssueUnsignedTx(utx, options...) +} + func (w *wallet) IssueImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/chain/p/wallet/with_options.go b/wallet/chain/p/wallet/with_options.go index ab456d7d469e..2beb789ebfdf 100644 --- a/wallet/chain/p/wallet/with_options.go +++ b/wallet/chain/p/wallet/with_options.go @@ -176,6 +176,16 @@ func (w *withOptions) IssueRegisterSubnetValidatorTx( ) } +func (w *withOptions) IssueSetSubnetValidatorWeightTx( + message []byte, + options ...common.Option, +) (*txs.Tx, error) { + return w.wallet.IssueSetSubnetValidatorWeightTx( + message, + common.UnionOptions(w.options, options)..., + ) +} + func (w *withOptions) IssueImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/subnet/primary/examples/convert-subnet/main.go b/wallet/subnet/primary/examples/convert-subnet/main.go index 14b6f6b588c6..9d117e72c691 100644 --- a/wallet/subnet/primary/examples/convert-subnet/main.go +++ b/wallet/subnet/primary/examples/convert-subnet/main.go @@ -22,8 +22,8 @@ func main() { key := genesis.EWOQKey uri := "http://localhost:9700" kc := secp256k1fx.NewKeychain(key) - subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") - chainID := ids.FromStringOrPanic("2f23iBApzAwJy1LRrgGZ3pGGK4S6UrakHMejZsQiwKy4rKfnzx") + subnetID := ids.FromStringOrPanic("2eZYSgCU738xN7aRw47NsBUPqnKkoqJMYUJexTsX19VdTNSZc9") + chainID := ids.FromStringOrPanic("2ko3NCPzHeneKWcYfy55pgAgU1LV9Q9XNrNv2sWG4W2XzE3ViV") addressHex := "" weight := units.Schmeckle diff --git a/wallet/subnet/primary/examples/create-chain/main.go b/wallet/subnet/primary/examples/create-chain/main.go index 61526d9cf38a..9a4ac92782d5 100644 --- a/wallet/subnet/primary/examples/create-chain/main.go +++ b/wallet/subnet/primary/examples/create-chain/main.go @@ -22,7 +22,7 @@ func main() { key := genesis.EWOQKey uri := primary.LocalAPIURI kc := secp256k1fx.NewKeychain(key) - subnetIDStr := "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof" + subnetIDStr := "2eZYSgCU738xN7aRw47NsBUPqnKkoqJMYUJexTsX19VdTNSZc9" genesis := &xsgenesis.Genesis{ Timestamp: time.Now().Unix(), Allocations: []xsgenesis.Allocation{ diff --git a/wallet/subnet/primary/examples/register-subnet-validator/main.go b/wallet/subnet/primary/examples/register-subnet-validator/main.go index 2eb92b164ff0..7e6f9ebdf0d5 100644 --- a/wallet/subnet/primary/examples/register-subnet-validator/main.go +++ b/wallet/subnet/primary/examples/register-subnet-validator/main.go @@ -25,8 +25,8 @@ func main() { key := genesis.EWOQKey uri := "http://localhost:9710" kc := secp256k1fx.NewKeychain(key) - subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") - chainID := ids.FromStringOrPanic("2f23iBApzAwJy1LRrgGZ3pGGK4S6UrakHMejZsQiwKy4rKfnzx") + subnetID := ids.FromStringOrPanic("2eZYSgCU738xN7aRw47NsBUPqnKkoqJMYUJexTsX19VdTNSZc9") + chainID := ids.FromStringOrPanic("2ko3NCPzHeneKWcYfy55pgAgU1LV9Q9XNrNv2sWG4W2XzE3ViV") addressHex := "" weight := units.Schmeckle @@ -109,6 +109,6 @@ func main() { log.Fatalf("failed to issue add subnet validator transaction: %s\n", err) } - validationID := hashing.ComputeHash256Array(addressedCallPayload.Bytes()) - log.Printf("added new subnet validator %s to %s with %s as %s in %s\n", nodeID, subnetID, addValidatorTx.ID(), validationID, time.Since(convertSubnetStartTime)) + var validationID ids.ID = hashing.ComputeHash256Array(addressedCallPayload.Bytes()) + log.Printf("added new subnet validator %s to subnet %s with txID %s as validationID %s in %s\n", nodeID, subnetID, addValidatorTx.ID(), validationID, time.Since(convertSubnetStartTime)) } diff --git a/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go b/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go new file mode 100644 index 000000000000..082268505a65 --- /dev/null +++ b/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go @@ -0,0 +1,96 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "encoding/hex" + "log" + "time" + + "github.com/ava-labs/avalanchego/genesis" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" +) + +func main() { + key := genesis.EWOQKey + uri := primary.LocalAPIURI + kc := secp256k1fx.NewKeychain(key) + chainID := ids.FromStringOrPanic("2ko3NCPzHeneKWcYfy55pgAgU1LV9Q9XNrNv2sWG4W2XzE3ViV") + addressHex := "" + validationID := ids.FromStringOrPanic("225kHLzuaBd6rhxZ8aq91kmgLJyPTFtTFVAWJDaPyKRdDiTpQo") + nonce := uint64(1) + weight := uint64(0) + + address, err := hex.DecodeString(addressHex) + if err != nil { + log.Fatalf("failed to decode address %q: %s\n", addressHex, err) + } + + // MakeWallet fetches the available UTXOs owned by [kc] on the network that + // [uri] is hosting and registers [subnetID]. + walletSyncStartTime := time.Now() + ctx := context.Background() + wallet, err := primary.MakeWallet(ctx, &primary.WalletConfig{ + URI: uri, + AVAXKeychain: kc, + EthKeychain: kc, + }) + if err != nil { + log.Fatalf("failed to initialize wallet: %s\n", err) + } + log.Printf("synced wallet in %s\n", time.Since(walletSyncStartTime)) + + // Get the P-chain wallet + pWallet := wallet.P() + context := pWallet.Builder().Context() + + addressedCallPayload, err := message.NewSetSubnetValidatorWeight( + validationID, + nonce, + weight, + ) + if err != nil { + log.Fatalf("failed to create SetSubnetValidatorWeight message: %s\n", err) + } + + addressedCall, err := payload.NewAddressedCall( + address, + addressedCallPayload.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create AddressedCall message: %s\n", err) + } + + unsignedWarp, err := warp.NewUnsignedMessage( + context.NetworkID, + chainID, + addressedCall.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create unsigned Warp message: %s\n", err) + } + + warp, err := warp.NewMessage( + unsignedWarp, + &warp.BitSetSignature{}, + ) + if err != nil { + log.Fatalf("failed to create Warp message: %s\n", err) + } + + setWeightStartTime := time.Now() + setWeightTx, err := pWallet.IssueSetSubnetValidatorWeightTx( + warp.Bytes(), + ) + if err != nil { + log.Fatalf("failed to issue set subnet validator weight transaction: %s\n", err) + } + log.Printf("issued set weight of validationID %s to %d with nonce %d and txID %s in %s\n", validationID, weight, nonce, setWeightTx.ID(), time.Since(setWeightStartTime)) +} From 225054611bd6fd6270be77dc024b26ce3cbbde5b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 26 Sep 2024 16:32:17 -0400 Subject: [PATCH 078/199] ACP-77: Implement Warp message verification --- vms/platformvm/block/executor/block.go | 40 ++++-- .../block/executor/warp_verifier.go | 95 +++++++++++++ .../txs/executor/standard_tx_executor.go | 10 -- vms/platformvm/txs/executor/warp_verifier.go | 132 ++++++++++++++++++ 4 files changed, 259 insertions(+), 18 deletions(-) create mode 100644 vms/platformvm/block/executor/warp_verifier.go create mode 100644 vms/platformvm/txs/executor/warp_verifier.go diff --git a/vms/platformvm/block/executor/block.go b/vms/platformvm/block/executor/block.go index 76cf6ff9078f..6fa2f26cdb9a 100644 --- a/vms/platformvm/block/executor/block.go +++ b/vms/platformvm/block/executor/block.go @@ -29,25 +29,49 @@ func (*Block) ShouldVerifyWithContext(context.Context) (bool, error) { return true, nil } -func (b *Block) VerifyWithContext(_ context.Context, ctx *smblock.Context) error { - pChainHeight := uint64(0) +func (b *Block) VerifyWithContext(ctx context.Context, blockContext *smblock.Context) error { + var pChainHeight uint64 if ctx != nil { - pChainHeight = ctx.PChainHeight + pChainHeight = blockContext.PChainHeight } blkID := b.ID() if blkState, ok := b.manager.blkIDToState[blkID]; ok { if !blkState.verifiedHeights.Contains(pChainHeight) { - // PlatformVM blocks are currently valid regardless of the ProposerVM's - // PChainHeight. If this changes, those validity checks should be done prior - // to adding [pChainHeight] to [verifiedHeights]. + // Only the validity of warp messages need to be verified because + // this block has already passed verification with a different + // height. + err := VerifyWarpMessages( + ctx, + b.manager.ctx.NetworkID, + b.manager.ctx.ValidatorState, + pChainHeight, + b, + ) + if err != nil { + return err + } + blkState.verifiedHeights.Add(pChainHeight) } - // This block has already been verified. - return nil + return nil // This block has already been executed. + } + + // Verify the warp messages in the block. + err := VerifyWarpMessages( + ctx, + b.manager.ctx.NetworkID, + b.manager.ctx.ValidatorState, + pChainHeight, + b, + ) + if err != nil { + return err } + // Since the warp messages are valid, we need to execute the rest of the + // validity checks. return b.Visit(&verifier{ backend: b.manager.backend, txExecutorBackend: b.manager.txExecutorBackend, diff --git a/vms/platformvm/block/executor/warp_verifier.go b/vms/platformvm/block/executor/warp_verifier.go new file mode 100644 index 000000000000..9587420da368 --- /dev/null +++ b/vms/platformvm/block/executor/warp_verifier.go @@ -0,0 +1,95 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package executor + +import ( + "context" + + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/vms/platformvm/block" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" +) + +var _ block.Visitor = (*warpVerifier)(nil) + +// VerifyWarpMessages verifies all warp messages in the block. If any of the +// warp messages are invalid, an error is returned. +func VerifyWarpMessages( + ctx context.Context, + networkID uint32, + validatorState validators.State, + pChainHeight uint64, + b block.Block, +) error { + return b.Visit(&warpVerifier{ + ctx: ctx, + networkID: networkID, + validatorState: validatorState, + pChainHeight: pChainHeight, + }) +} + +type warpVerifier struct { + ctx context.Context + networkID uint32 + validatorState validators.State + pChainHeight uint64 +} + +func (*warpVerifier) BanffAbortBlock(*block.BanffAbortBlock) error { + return nil +} + +func (*warpVerifier) BanffCommitBlock(*block.BanffCommitBlock) error { + return nil +} + +func (w *warpVerifier) BanffProposalBlock(blk *block.BanffProposalBlock) error { + for _, tx := range blk.Transactions { + if err := w.verify(tx); err != nil { + return err + } + } + return w.ApricotProposalBlock(&blk.ApricotProposalBlock) +} + +func (w *warpVerifier) BanffStandardBlock(blk *block.BanffStandardBlock) error { + return w.ApricotStandardBlock(&blk.ApricotStandardBlock) +} + +func (*warpVerifier) ApricotAbortBlock(*block.ApricotAbortBlock) error { + return nil +} + +func (*warpVerifier) ApricotCommitBlock(*block.ApricotCommitBlock) error { + return nil +} + +func (w *warpVerifier) ApricotProposalBlock(blk *block.ApricotProposalBlock) error { + return w.verify(blk.Tx) +} + +func (w *warpVerifier) ApricotStandardBlock(blk *block.ApricotStandardBlock) error { + for _, tx := range blk.Transactions { + if err := w.verify(tx); err != nil { + return err + } + } + return nil +} + +func (w *warpVerifier) ApricotAtomicBlock(blk *block.ApricotAtomicBlock) error { + return w.verify(blk.Tx) +} + +func (w *warpVerifier) verify(tx *txs.Tx) error { + return executor.VerifyWarpMessages( + w.ctx, + w.networkID, + w.validatorState, + w.pChainHeight, + tx.Unsigned, + ) +} diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 87849ae19fbb..a88f93f0bc79 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -653,15 +653,10 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal if err != nil { return err } - if warpMessage.NetworkID != e.Ctx.NetworkID { - return fmt.Errorf("expected networkID %d but got %d", e.Ctx.NetworkID, warpMessage.NetworkID) - } - addressedCall, err := payload.ParseAddressedCall(warpMessage.Payload) if err != nil { return err } - msg, err := message.ParseRegisterSubnetValidator(addressedCall.Payload) if err != nil { return err @@ -798,15 +793,10 @@ func (e *StandardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat if err != nil { return err } - if warpMessage.NetworkID != e.Ctx.NetworkID { - return fmt.Errorf("expected networkID %d but got %d", e.Ctx.NetworkID, warpMessage.NetworkID) - } - addressedCall, err := payload.ParseAddressedCall(warpMessage.Payload) if err != nil { return err } - msg, err := message.ParseSetSubnetValidatorWeight(addressedCall.Payload) if err != nil { return err diff --git a/vms/platformvm/txs/executor/warp_verifier.go b/vms/platformvm/txs/executor/warp_verifier.go new file mode 100644 index 000000000000..f637d425a4cd --- /dev/null +++ b/vms/platformvm/txs/executor/warp_verifier.go @@ -0,0 +1,132 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package executor + +import ( + "context" + + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" +) + +const ( + WarpQuorumNumerator = 67 + WarpQuorumDenominator = 100 +) + +var _ txs.Visitor = (*warpVerifier)(nil) + +// VerifyWarpMessages verifies all warp messages in the tx. If any of the warp +// messages are invalid, an error is returned. +func VerifyWarpMessages( + ctx context.Context, + networkID uint32, + validatorState validators.State, + pChainHeight uint64, + tx txs.UnsignedTx, +) error { + return tx.Visit(&warpVerifier{ + ctx: ctx, + networkID: networkID, + validatorState: validatorState, + pChainHeight: pChainHeight, + }) +} + +type warpVerifier struct { + ctx context.Context + networkID uint32 + validatorState validators.State + pChainHeight uint64 +} + +func (*warpVerifier) AddValidatorTx(*txs.AddValidatorTx) error { + return nil +} + +func (*warpVerifier) AddSubnetValidatorTx(*txs.AddSubnetValidatorTx) error { + return nil +} + +func (*warpVerifier) AddDelegatorTx(*txs.AddDelegatorTx) error { + return nil +} + +func (*warpVerifier) CreateChainTx(*txs.CreateChainTx) error { + return nil +} + +func (*warpVerifier) CreateSubnetTx(*txs.CreateSubnetTx) error { + return nil +} + +func (*warpVerifier) ImportTx(*txs.ImportTx) error { + return nil +} + +func (*warpVerifier) ExportTx(*txs.ExportTx) error { + return nil +} + +func (*warpVerifier) AdvanceTimeTx(*txs.AdvanceTimeTx) error { + return nil +} + +func (*warpVerifier) RewardValidatorTx(*txs.RewardValidatorTx) error { + return nil +} + +func (*warpVerifier) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { + return nil +} + +func (*warpVerifier) TransformSubnetTx(*txs.TransformSubnetTx) error { + return nil +} + +func (*warpVerifier) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { + return nil +} + +func (*warpVerifier) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { + return nil +} + +func (*warpVerifier) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { + return nil +} + +func (*warpVerifier) BaseTx(*txs.BaseTx) error { + return nil +} + +func (*warpVerifier) ConvertSubnetTx(*txs.ConvertSubnetTx) error { + return nil +} + +func (w *warpVerifier) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetValidatorTx) error { + return w.verify(tx.Message) +} + +func (w *warpVerifier) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidatorWeightTx) error { + return w.verify(tx.Message) +} + +func (w *warpVerifier) verify(message []byte) error { + msg, err := warp.ParseMessage(message) + if err != nil { + return err + } + + return msg.Signature.Verify( + w.ctx, + &msg.UnsignedMessage, + w.networkID, + w.validatorState, + w.pChainHeight, + WarpQuorumNumerator, + WarpQuorumDenominator, + ) +} From 17c0fcfbfb5dc4f86a359e17b4be7b3975f9e513 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 26 Sep 2024 16:40:08 -0400 Subject: [PATCH 079/199] fix --- vms/platformvm/block/executor/block.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/block/executor/block.go b/vms/platformvm/block/executor/block.go index 6fa2f26cdb9a..3a8f459915fd 100644 --- a/vms/platformvm/block/executor/block.go +++ b/vms/platformvm/block/executor/block.go @@ -31,7 +31,7 @@ func (*Block) ShouldVerifyWithContext(context.Context) (bool, error) { func (b *Block) VerifyWithContext(ctx context.Context, blockContext *smblock.Context) error { var pChainHeight uint64 - if ctx != nil { + if blockContext != nil { pChainHeight = blockContext.PChainHeight } From 8b123c8a03b1305c1ed4e685ad927bfe39830a9c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 27 Sep 2024 16:20:18 -0400 Subject: [PATCH 080/199] nits --- vms/platformvm/block/builder/builder.go | 59 +++++++++++++++++-- vms/platformvm/block/executor/manager.go | 16 +++++ .../primary/examples/convert-subnet/main.go | 2 +- .../register-subnet-validator/main.go | 30 +++++++++- 4 files changed, 99 insertions(+), 8 deletions(-) diff --git a/vms/platformvm/block/builder/builder.go b/vms/platformvm/block/builder/builder.go index 3c88e8277929..c83327908bd9 100644 --- a/vms/platformvm/block/builder/builder.go +++ b/vms/platformvm/block/builder/builder.go @@ -27,6 +27,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" + smblock "github.com/ava-labs/avalanchego/snow/engine/snowman/block" blockexecutor "github.com/ava-labs/avalanchego/vms/platformvm/block/executor" txexecutor "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" ) @@ -51,6 +52,7 @@ var ( ) type Builder interface { + smblock.BuildBlockWithContextChainVM mempool.Mempool // StartBlockTimer starts to issue block creation requests to advance the @@ -208,10 +210,14 @@ func (b *builder) ShutdownBlockTimer() { }) } -// BuildBlock builds a block to be added to consensus. -// This method removes the transactions from the returned -// blocks from the mempool. -func (b *builder) BuildBlock(context.Context) (snowman.Block, error) { +func (b *builder) BuildBlock(ctx context.Context) (snowman.Block, error) { + return b.BuildBlockWithContext(ctx, nil) +} + +func (b *builder) BuildBlockWithContext( + ctx context.Context, + blockContext *smblock.Context, +) (snowman.Block, error) { // If there are still transactions in the mempool, then we need to // re-trigger block building. defer b.Mempool.RequestBuildBlock(false /*=emptyBlockPermitted*/) @@ -239,13 +245,20 @@ func (b *builder) BuildBlock(context.Context) (snowman.Block, error) { return nil, fmt.Errorf("could not calculate next staker change time: %w", err) } + var pChainHeight uint64 + if blockContext != nil { + pChainHeight = blockContext.PChainHeight + } + statelessBlk, err := buildBlock( + ctx, b, preferredID, nextHeight, timestamp, timeWasCapped, preferredState, + pChainHeight, ) if err != nil { return nil, err @@ -270,36 +283,47 @@ func (b *builder) PackAllBlockTxs() ([]*txs.Tx, error) { return nil, fmt.Errorf("could not calculate next staker change time: %w", err) } + recommendedPChainHeight, err := b.txExecutorBackend.Ctx.ValidatorState.GetMinimumHeight(context.TODO()) + if err != nil { + return nil, err + } + if !b.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) { return packDurangoBlockTxs( + context.TODO(), preferredID, preferredState, b.Mempool, b.txExecutorBackend, b.blkManager, timestamp, + recommendedPChainHeight, math.MaxInt, ) } return packEtnaBlockTxs( + context.TODO(), preferredID, preferredState, b.Mempool, b.txExecutorBackend, b.blkManager, timestamp, + recommendedPChainHeight, math.MaxUint64, ) } // [timestamp] is min(max(now, parent timestamp), next staker change time) func buildBlock( + ctx context.Context, builder *builder, parentID ids.ID, height uint64, timestamp time.Time, forceAdvanceTime bool, parentState state.Chain, + pChainHeight uint64, ) (block.Block, error) { var ( blockTxs []*txs.Tx @@ -307,22 +331,26 @@ func buildBlock( ) if builder.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) { blockTxs, err = packEtnaBlockTxs( + ctx, parentID, parentState, builder.Mempool, builder.txExecutorBackend, builder.blkManager, timestamp, + pChainHeight, 0, // minCapacity is 0 as we want to honor the capacity in state. ) } else { blockTxs, err = packDurangoBlockTxs( + ctx, parentID, parentState, builder.Mempool, builder.txExecutorBackend, builder.blkManager, timestamp, + pChainHeight, targetBlockSize, ) } @@ -368,12 +396,14 @@ func buildBlock( } func packDurangoBlockTxs( + ctx context.Context, parentID ids.ID, parentState state.Chain, mempool mempool.Mempool, backend *txexecutor.Backend, manager blockexecutor.Manager, timestamp time.Time, + pChainHeight uint64, remainingSize int, ) ([]*txs.Tx, error) { stateDiff, err := state.NewDiffOn(parentState) @@ -401,11 +431,13 @@ func packDurangoBlockTxs( } shouldAdd, err := executeTx( + ctx, parentID, stateDiff, mempool, backend, manager, + pChainHeight, &inputs, feeCalculator, tx, @@ -425,12 +457,14 @@ func packDurangoBlockTxs( } func packEtnaBlockTxs( + ctx context.Context, parentID ids.ID, parentState state.Chain, mempool mempool.Mempool, backend *txexecutor.Backend, manager blockexecutor.Manager, timestamp time.Time, + pChainHeight uint64, minCapacity gas.Gas, ) ([]*txs.Tx, error) { stateDiff, err := state.NewDiffOn(parentState) @@ -474,11 +508,13 @@ func packEtnaBlockTxs( } shouldAdd, err := executeTx( + ctx, parentID, stateDiff, mempool, backend, manager, + pChainHeight, &inputs, feeCalculator, tx, @@ -498,11 +534,13 @@ func packEtnaBlockTxs( } func executeTx( + ctx context.Context, parentID ids.ID, stateDiff state.Diff, mempool mempool.Mempool, backend *txexecutor.Backend, manager blockexecutor.Manager, + pChainHeight uint64, inputs *set.Set[ids.ID], feeCalculator fee.Calculator, tx *txs.Tx, @@ -511,6 +549,19 @@ func executeTx( // Invariant: [tx] has already been syntactically verified. + err := txexecutor.VerifyWarpMessages( + ctx, + backend.Ctx.NetworkID, + backend.Ctx.ValidatorState, + pChainHeight, + tx.Unsigned, + ) + if err != nil { + txID := tx.ID() + mempool.MarkDropped(txID, err) + return false, nil + } + txDiff, err := state.NewDiffOn(stateDiff) if err != nil { return false, err diff --git a/vms/platformvm/block/executor/manager.go b/vms/platformvm/block/executor/manager.go index 5c419500e5f7..2954671a5b89 100644 --- a/vms/platformvm/block/executor/manager.go +++ b/vms/platformvm/block/executor/manager.go @@ -4,6 +4,7 @@ package executor import ( + "context" "errors" "github.com/ava-labs/avalanchego/ids" @@ -122,6 +123,21 @@ func (m *manager) VerifyTx(tx *txs.Tx) error { return ErrChainNotSynced } + recommendedPChainHeight, err := m.ctx.ValidatorState.GetMinimumHeight(context.TODO()) + if err != nil { + return err + } + err = executor.VerifyWarpMessages( + context.TODO(), + m.ctx.NetworkID, + m.ctx.ValidatorState, + recommendedPChainHeight, + tx.Unsigned, + ) + if err != nil { + return err + } + stateDiff, err := state.NewDiff(m.preferred, m) if err != nil { return err diff --git a/wallet/subnet/primary/examples/convert-subnet/main.go b/wallet/subnet/primary/examples/convert-subnet/main.go index 9d117e72c691..94345c8fe2e8 100644 --- a/wallet/subnet/primary/examples/convert-subnet/main.go +++ b/wallet/subnet/primary/examples/convert-subnet/main.go @@ -23,7 +23,7 @@ func main() { uri := "http://localhost:9700" kc := secp256k1fx.NewKeychain(key) subnetID := ids.FromStringOrPanic("2eZYSgCU738xN7aRw47NsBUPqnKkoqJMYUJexTsX19VdTNSZc9") - chainID := ids.FromStringOrPanic("2ko3NCPzHeneKWcYfy55pgAgU1LV9Q9XNrNv2sWG4W2XzE3ViV") + chainID := ids.FromStringOrPanic("JCuZD3rg8dEEeY5cJwQxwSj6Shqo8DtRynBCt1nXkBXCouZVw") addressHex := "" weight := units.Schmeckle diff --git a/wallet/subnet/primary/examples/register-subnet-validator/main.go b/wallet/subnet/primary/examples/register-subnet-validator/main.go index 7e6f9ebdf0d5..f3841cd2fe4e 100644 --- a/wallet/subnet/primary/examples/register-subnet-validator/main.go +++ b/wallet/subnet/primary/examples/register-subnet-validator/main.go @@ -7,12 +7,15 @@ import ( "context" "encoding/hex" "log" + "os" "time" "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" @@ -23,10 +26,10 @@ import ( func main() { key := genesis.EWOQKey - uri := "http://localhost:9710" + uri := "http://localhost:9650" kc := secp256k1fx.NewKeychain(key) subnetID := ids.FromStringOrPanic("2eZYSgCU738xN7aRw47NsBUPqnKkoqJMYUJexTsX19VdTNSZc9") - chainID := ids.FromStringOrPanic("2ko3NCPzHeneKWcYfy55pgAgU1LV9Q9XNrNv2sWG4W2XzE3ViV") + chainID := ids.FromStringOrPanic("JCuZD3rg8dEEeY5cJwQxwSj6Shqo8DtRynBCt1nXkBXCouZVw") addressHex := "" weight := units.Schmeckle @@ -38,6 +41,16 @@ func main() { ctx := context.Background() infoClient := info.NewClient(uri) + skBytes, err := os.ReadFile("/Users/stephen/.avalanchego/staking/signer.key") + if err != nil { + log.Fatalf("failed to read signer key: %s\n", err) + } + + sk, err := bls.SecretKeyFromBytes(skBytes) + if err != nil { + log.Fatalf("failed to parse secret key: %s\n", err) + } + nodeInfoStartTime := time.Now() nodeID, nodePoP, err := infoClient.GetNodeID(ctx) if err != nil { @@ -90,9 +103,20 @@ func main() { log.Fatalf("failed to create unsigned Warp message: %s\n", err) } + signers := set.NewBits() + signers.Add(0) // [signers] has weight from [vdr[0]] + + unsignedBytes := unsignedWarp.Bytes() + sig := bls.Sign(sk, unsignedBytes) + sigBytes := [bls.SignatureLen]byte{} + copy(sigBytes[:], bls.SignatureToBytes(sig)) + warp, err := warp.NewMessage( unsignedWarp, - &warp.BitSetSignature{}, + &warp.BitSetSignature{ + Signers: signers.Bytes(), + Signature: sigBytes, + }, ) if err != nil { log.Fatalf("failed to create Warp message: %s\n", err) From 7667781570ab050544c7767bf7d1fec59aea85b6 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 27 Sep 2024 16:21:12 -0400 Subject: [PATCH 081/199] nit --- vms/platformvm/vm.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index 975e13f7ebd5..2a65111bb66a 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -52,9 +52,10 @@ import ( ) var ( - _ snowmanblock.ChainVM = (*VM)(nil) - _ secp256k1fx.VM = (*VM)(nil) - _ validators.State = (*VM)(nil) + _ snowmanblock.ChainVM = (*VM)(nil) + _ snowmanblock.BuildBlockWithContextChainVM = (*VM)(nil) + _ secp256k1fx.VM = (*VM)(nil) + _ validators.State = (*VM)(nil) ) type VM struct { From 3dc6adc331df3adb9b700d8e33b0511bd7695608 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 28 Sep 2024 15:54:05 -0400 Subject: [PATCH 082/199] ACP-77: Implement Warp message signing --- vms/platformvm/config/execution_config.go | 27 +- .../config/execution_config_test.go | 4 +- .../config.go => config/network_config.go} | 6 +- vms/platformvm/network/network.go | 20 +- vms/platformvm/network/network_test.go | 3 +- vms/platformvm/network/warp.go | 278 ++++++++++++++++++ vms/platformvm/validators/manager.go | 4 +- vms/platformvm/vm.go | 3 + vms/platformvm/warp/message/payload.go | 2 +- .../warp/message/register_subnet_validator.go | 2 +- .../message/set_subnet_validator_weight.go | 2 +- .../warp/message/subnet_conversion.go | 2 +- .../message/subnet_validator_registration.go | 2 +- .../warp/message/subnet_validator_weight.go | 2 +- .../primary/examples/convert-subnet/main.go | 4 +- .../primary/examples/create-chain/main.go | 2 +- .../register-subnet-validator/main.go | 12 +- .../main.go | 190 ++++++++++++ 18 files changed, 529 insertions(+), 36 deletions(-) rename vms/platformvm/{network/config.go => config/network_config.go} (98%) create mode 100644 vms/platformvm/network/warp.go create mode 100644 wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go diff --git a/vms/platformvm/config/execution_config.go b/vms/platformvm/config/execution_config.go index fafdaf9b99d2..c04a62b2771c 100644 --- a/vms/platformvm/config/execution_config.go +++ b/vms/platformvm/config/execution_config.go @@ -8,11 +8,10 @@ import ( "time" "github.com/ava-labs/avalanchego/utils/units" - "github.com/ava-labs/avalanchego/vms/platformvm/network" ) var DefaultExecutionConfig = ExecutionConfig{ - Network: network.DefaultConfig, + Network: DefaultNetworkConfig, BlockCacheSize: 64 * units.MiB, TxCacheSize: 128 * units.MiB, TransformedSubnetTxCacheSize: 4 * units.MiB, @@ -28,18 +27,18 @@ var DefaultExecutionConfig = ExecutionConfig{ // ExecutionConfig provides execution parameters of PlatformVM type ExecutionConfig struct { - Network network.Config `json:"network"` - BlockCacheSize int `json:"block-cache-size"` - TxCacheSize int `json:"tx-cache-size"` - TransformedSubnetTxCacheSize int `json:"transformed-subnet-tx-cache-size"` - RewardUTXOsCacheSize int `json:"reward-utxos-cache-size"` - ChainCacheSize int `json:"chain-cache-size"` - ChainDBCacheSize int `json:"chain-db-cache-size"` - BlockIDCacheSize int `json:"block-id-cache-size"` - FxOwnerCacheSize int `json:"fx-owner-cache-size"` - SubnetManagerCacheSize int `json:"subnet-manager-cache-size"` - ChecksumsEnabled bool `json:"checksums-enabled"` - MempoolPruneFrequency time.Duration `json:"mempool-prune-frequency"` + Network NetworkConfig `json:"network"` + BlockCacheSize int `json:"block-cache-size"` + TxCacheSize int `json:"tx-cache-size"` + TransformedSubnetTxCacheSize int `json:"transformed-subnet-tx-cache-size"` + RewardUTXOsCacheSize int `json:"reward-utxos-cache-size"` + ChainCacheSize int `json:"chain-cache-size"` + ChainDBCacheSize int `json:"chain-db-cache-size"` + BlockIDCacheSize int `json:"block-id-cache-size"` + FxOwnerCacheSize int `json:"fx-owner-cache-size"` + SubnetManagerCacheSize int `json:"subnet-manager-cache-size"` + ChecksumsEnabled bool `json:"checksums-enabled"` + MempoolPruneFrequency time.Duration `json:"mempool-prune-frequency"` } // GetExecutionConfig returns an ExecutionConfig diff --git a/vms/platformvm/config/execution_config_test.go b/vms/platformvm/config/execution_config_test.go index 5929a75f9063..e86fd720e634 100644 --- a/vms/platformvm/config/execution_config_test.go +++ b/vms/platformvm/config/execution_config_test.go @@ -10,8 +10,6 @@ import ( "time" "github.com/stretchr/testify/require" - - "github.com/ava-labs/avalanchego/vms/platformvm/network" ) // Requires all values in a struct to be initialized @@ -62,7 +60,7 @@ func TestExecutionConfigUnmarshal(t *testing.T) { require := require.New(t) expected := &ExecutionConfig{ - Network: network.Config{ + Network: NetworkConfig{ MaxValidatorSetStaleness: 1, TargetGossipSize: 2, PushGossipPercentStake: .3, diff --git a/vms/platformvm/network/config.go b/vms/platformvm/config/network_config.go similarity index 98% rename from vms/platformvm/network/config.go rename to vms/platformvm/config/network_config.go index 797138ab93f9..349a03953cdf 100644 --- a/vms/platformvm/network/config.go +++ b/vms/platformvm/config/network_config.go @@ -1,7 +1,7 @@ // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package network +package config import ( "time" @@ -9,7 +9,7 @@ import ( "github.com/ava-labs/avalanchego/utils/units" ) -var DefaultConfig = Config{ +var DefaultNetworkConfig = NetworkConfig{ MaxValidatorSetStaleness: time.Minute, TargetGossipSize: 20 * units.KiB, PushGossipPercentStake: .9, @@ -29,7 +29,7 @@ var DefaultConfig = Config{ MaxBloomFilterFalsePositiveProbability: .05, } -type Config struct { +type NetworkConfig struct { // MaxValidatorSetStaleness limits how old of a validator set the network // will use for peer sampling and rate limiting. MaxValidatorSetStaleness time.Duration `json:"max-validator-set-staleness"` diff --git a/vms/platformvm/network/network.go b/vms/platformvm/network/network.go index c821c8272511..d4780646a0ac 100644 --- a/vms/platformvm/network/network.go +++ b/vms/platformvm/network/network.go @@ -6,6 +6,7 @@ package network import ( "context" "errors" + "sync" "time" "github.com/prometheus/client_golang/prometheus" @@ -17,8 +18,11 @@ import ( "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/config" + "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" ) var errMempoolDisabledWithPartialSync = errors.New("mempool is disabled partial syncing") @@ -47,8 +51,11 @@ func New( mempool mempool.Mempool, partialSyncPrimaryNetwork bool, appSender common.AppSender, + stateLock sync.Locker, + state state.Chain, + signer warp.Signer, registerer prometheus.Registerer, - config Config, + config config.NetworkConfig, ) (*Network, error) { p2pNetwork, err := p2p.NewNetwork(log, appSender, registerer, "p2p") if err != nil { @@ -156,6 +163,17 @@ func New( return nil, err } + // We allow all peers to request warp messaging signatures + signatureRequestHandler := signatureRequestHandler{ + stateLock: stateLock, + state: state, + signer: signer, + } + + if err := p2pNetwork.AddHandler(p2p.SignatureRequestHandlerID, signatureRequestHandler); err != nil { + return nil, err + } + return &Network{ Network: p2pNetwork, log: log, diff --git a/vms/platformvm/network/network_test.go b/vms/platformvm/network/network_test.go index f649c677450c..6f990e2196ab 100644 --- a/vms/platformvm/network/network_test.go +++ b/vms/platformvm/network/network_test.go @@ -17,6 +17,7 @@ import ( "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/common/commonmock" "github.com/ava-labs/avalanchego/snow/snowtest" + "github.com/ava-labs/avalanchego/vms/platformvm/config" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool/mempoolmock" "github.com/ava-labs/avalanchego/vms/txs/mempool" @@ -27,7 +28,7 @@ import ( var ( errTest = errors.New("test error") - testConfig = Config{ + testConfig = config.NetworkConfig{ MaxValidatorSetStaleness: time.Second, TargetGossipSize: 1, PushGossipNumValidators: 1, diff --git a/vms/platformvm/network/warp.go b/vms/platformvm/network/warp.go new file mode 100644 index 000000000000..0cb9e945d454 --- /dev/null +++ b/vms/platformvm/network/warp.go @@ -0,0 +1,278 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package network + +import ( + "context" + "fmt" + "math" + "sync" + "time" + + "google.golang.org/protobuf/proto" + + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/vms/platformvm/state" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" +) + +const ( + ErrFailedToParseRequest = iota + 1 + ErrFailedToParseWarp + ErrFailedToParseWarpAddressedCall + ErrWarpAddressedCallHasSourceAddress + ErrFailedToParseWarpAddressedCallPayload + ErrUnsupportedWarpAddressedCallPayloadType + ErrFailedToParseJustification + ErrMismatchedValidationID + ErrValidationDoesNotExist + ErrValidationExists + ErrValidationCouldBeRegistered + ErrImpossibleNonce + ErrWrongNonce + ErrWrongWeight + ErrFailedToSignWarp + errUnimplemented // TODO: Remove +) + +var _ p2p.Handler = (*signatureRequestHandler)(nil) + +type signatureRequestHandler struct { + p2p.NoOpHandler + + stateLock sync.Locker + state state.Chain + + signer warp.Signer +} + +// TODO: This should be allowed only for local networks +func (s signatureRequestHandler) AppRequest( + _ context.Context, + _ ids.NodeID, + _ time.Time, + requestBytes []byte, +) ([]byte, *common.AppError) { + // Per ACP-118, the requestBytes are the serialized form of + // sdk.SignatureRequest. + var req sdk.SignatureRequest + if err := proto.Unmarshal(requestBytes, &req); err != nil { + return nil, &common.AppError{ + Code: ErrFailedToParseRequest, + Message: "failed to unmarshal request: " + err.Error(), + } + } + + unsignedMessage, err := warp.ParseUnsignedMessage(req.Message) + if err != nil { + return nil, &common.AppError{ + Code: ErrFailedToParseWarp, + Message: "failed to parse warp message: " + err.Error(), + } + } + + msg, err := payload.ParseAddressedCall(unsignedMessage.Payload) + if err != nil { + return nil, &common.AppError{ + Code: ErrFailedToParseWarpAddressedCall, + Message: "failed to parse warp addressed call: " + err.Error(), + } + } + if len(msg.SourceAddress) != 0 { + return nil, &common.AppError{ + Code: ErrWarpAddressedCallHasSourceAddress, + Message: "source address should be empty", + } + } + + payloadIntf, err := message.Parse(msg.Payload) + if err != nil { + return nil, &common.AppError{ + Code: ErrFailedToParseWarpAddressedCallPayload, + Message: "failed to parse warp addressed call payload: " + err.Error(), + } + } + + var payloadErr *common.AppError + switch payload := payloadIntf.(type) { + case *message.SubnetConversion: + payloadErr = s.verifySubnetConversion(payload, req.Justification) + case *message.SubnetValidatorRegistration: + payloadErr = s.verifySubnetValidatorRegistration(payload, req.Justification) + case *message.SubnetValidatorWeight: + payloadErr = s.verifySubnetValidatorWeight(payload) + default: + payloadErr = &common.AppError{ + Code: ErrUnsupportedWarpAddressedCallPayloadType, + Message: fmt.Sprintf("unsupported warp addressed call payload type: %T", payloadIntf), + } + } + if payloadErr != nil { + return nil, payloadErr + } + + sig, err := s.signer.Sign(unsignedMessage) + if err != nil { + return nil, &common.AppError{ + Code: ErrFailedToSignWarp, + Message: "failed to sign warp message: " + err.Error(), + } + } + + // Per ACP-118, the responseBytes are the serialized form of + // sdk.SignatureResponse. + resp := &sdk.SignatureResponse{ + Signature: sig, + } + respBytes, err := proto.Marshal(resp) + if err != nil { + return nil, &common.AppError{ + Code: common.ErrUndefined.Code, + Message: "failed to marshal response: " + err.Error(), + } + } + return respBytes, nil +} + +func (s signatureRequestHandler) verifySubnetConversion( + msg *message.SubnetConversion, + justification []byte, +) *common.AppError { + _ = s + _ = msg + _ = justification + return &common.AppError{ + Code: errUnimplemented, + Message: "unimplemented", + } +} + +func (s signatureRequestHandler) verifySubnetValidatorRegistration( + msg *message.SubnetValidatorRegistration, + justification []byte, +) *common.AppError { + var justificationID ids.ID = hashing.ComputeHash256Array(justification) + if msg.ValidationID != justificationID { + return &common.AppError{ + Code: ErrMismatchedValidationID, + Message: fmt.Sprintf("validationID %q != justificationID %q", msg.ValidationID, justificationID), + } + } + + registerSubnetValidator, err := message.ParseRegisterSubnetValidator(justification) + if err != nil { + return &common.AppError{ + Code: ErrFailedToParseJustification, + Message: "failed to parse justification: " + err.Error(), + } + } + + s.stateLock.Lock() + defer s.stateLock.Unlock() + + if msg.Registered { + // Verify that the validator exists + _, err := s.state.GetSubnetOnlyValidator(msg.ValidationID) + if err == database.ErrNotFound { + return &common.AppError{ + Code: ErrValidationDoesNotExist, + Message: fmt.Sprintf("validation %q does not exist", msg.ValidationID), + } + } + if err != nil { + return &common.AppError{ + Code: common.ErrUndefined.Code, + Message: "failed to get subnet only validator: " + err.Error(), + } + } + return nil + } + + // Verify that the validator does not and can never exists + _, err = s.state.GetSubnetOnlyValidator(msg.ValidationID) + if err == nil { + return &common.AppError{ + Code: ErrValidationExists, + Message: fmt.Sprintf("validation %q exists", msg.ValidationID), + } + } + if err != database.ErrNotFound { + return &common.AppError{ + Code: common.ErrUndefined.Code, + Message: "failed to lookup subnet only validator: " + err.Error(), + } + } + + currentTimeUnix := uint64(s.state.GetTimestamp().Unix()) + if registerSubnetValidator.Expiry <= currentTimeUnix { + // The validator is not registered and the expiry time has passed + return nil + } + + hasExpiry, err := s.state.HasExpiry(state.ExpiryEntry{ + Timestamp: registerSubnetValidator.Expiry, + ValidationID: msg.ValidationID, + }) + if err != nil { + return &common.AppError{ + Code: common.ErrUndefined.Code, + Message: "failed to lookup expiry: " + err.Error(), + } + } + if !hasExpiry { + return &common.AppError{ + Code: ErrValidationCouldBeRegistered, + Message: fmt.Sprintf("validation %q can be registered until %d", msg.ValidationID, registerSubnetValidator.Expiry), + } + } + + return nil // The validator has been removed +} + +func (s signatureRequestHandler) verifySubnetValidatorWeight( + msg *message.SubnetValidatorWeight, +) *common.AppError { + if msg.Nonce == math.MaxUint64 { + return &common.AppError{ + Code: ErrImpossibleNonce, + Message: "impossible nonce", + } + } + + s.stateLock.Lock() + defer s.stateLock.Unlock() + + sov, err := s.state.GetSubnetOnlyValidator(msg.ValidationID) + switch { + case err == database.ErrNotFound: + return &common.AppError{ + Code: ErrValidationDoesNotExist, + Message: fmt.Sprintf("validation %q does not exist", msg.ValidationID), + } + case err != nil: + return &common.AppError{ + Code: common.ErrUndefined.Code, + Message: "failed to get subnet only validator: " + err.Error(), + } + case msg.Nonce+1 != sov.MinNonce: + return &common.AppError{ + Code: ErrWrongNonce, + Message: fmt.Sprintf("provided nonce %d != expected nonce (%d - 1)", msg.Nonce, sov.MinNonce), + } + case msg.Weight != sov.Weight: + return &common.AppError{ + Code: ErrWrongNonce, + Message: fmt.Sprintf("provided weight %d != expected weight %d", msg.Weight, sov.Weight), + } + default: + return nil // The nonce and weight are correct + } +} diff --git a/vms/platformvm/validators/manager.go b/vms/platformvm/validators/manager.go index feeb182dd84d..e582f997c95a 100644 --- a/vms/platformvm/validators/manager.go +++ b/vms/platformvm/validators/manager.go @@ -26,8 +26,8 @@ import ( const ( validatorSetsCacheSize = 64 maxRecentlyAcceptedWindowSize = 64 - minRecentlyAcceptedWindowSize = 16 - recentlyAcceptedWindowTTL = 2 * time.Minute + minRecentlyAcceptedWindowSize = 0 + recentlyAcceptedWindowTTL = 15 * time.Second ) var ( diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index 2a65111bb66a..9a63b0c36d15 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -193,6 +193,9 @@ func (vm *VM) Initialize( mempool, txExecutorBackend.Config.PartialSyncPrimaryNetwork, appSender, + &chainCtx.Lock, + vm.state, + chainCtx.WarpSigner, registerer, execConfig.Network, ) diff --git a/vms/platformvm/warp/message/payload.go b/vms/platformvm/warp/message/payload.go index 6903cc22001d..1da0f71aa9b5 100644 --- a/vms/platformvm/warp/message/payload.go +++ b/vms/platformvm/warp/message/payload.go @@ -43,7 +43,7 @@ func Parse(bytes []byte) (Payload, error) { return p, nil } -func initialize(p Payload) error { +func Initialize(p Payload) error { bytes, err := Codec.Marshal(CodecVersion, &p) if err != nil { return fmt.Errorf("couldn't marshal %T payload: %w", p, err) diff --git a/vms/platformvm/warp/message/register_subnet_validator.go b/vms/platformvm/warp/message/register_subnet_validator.go index 4a6bd9240435..242a3ba38f82 100644 --- a/vms/platformvm/warp/message/register_subnet_validator.go +++ b/vms/platformvm/warp/message/register_subnet_validator.go @@ -37,7 +37,7 @@ func NewRegisterSubnetValidator( BLSPublicKey: blsPublicKey, Expiry: expiry, } - return msg, initialize(msg) + return msg, Initialize(msg) } // ParseRegisterSubnetValidator parses bytes into an initialized diff --git a/vms/platformvm/warp/message/set_subnet_validator_weight.go b/vms/platformvm/warp/message/set_subnet_validator_weight.go index bde19f7ae67a..8e5101bee108 100644 --- a/vms/platformvm/warp/message/set_subnet_validator_weight.go +++ b/vms/platformvm/warp/message/set_subnet_validator_weight.go @@ -30,7 +30,7 @@ func NewSetSubnetValidatorWeight( Nonce: nonce, Weight: weight, } - return msg, initialize(msg) + return msg, Initialize(msg) } // ParseSetSubnetValidatorWeight parses bytes into an initialized diff --git a/vms/platformvm/warp/message/subnet_conversion.go b/vms/platformvm/warp/message/subnet_conversion.go index d4ca5587453d..046328f26fef 100644 --- a/vms/platformvm/warp/message/subnet_conversion.go +++ b/vms/platformvm/warp/message/subnet_conversion.go @@ -22,7 +22,7 @@ func NewSubnetConversion(id ids.ID) (*SubnetConversion, error) { msg := &SubnetConversion{ ID: id, } - return msg, initialize(msg) + return msg, Initialize(msg) } // ParseSubnetConversion parses bytes into an initialized SubnetConversion. diff --git a/vms/platformvm/warp/message/subnet_validator_registration.go b/vms/platformvm/warp/message/subnet_validator_registration.go index 2ab46c88dc3b..f0e1919b1e24 100644 --- a/vms/platformvm/warp/message/subnet_validator_registration.go +++ b/vms/platformvm/warp/message/subnet_validator_registration.go @@ -34,7 +34,7 @@ func NewSubnetValidatorRegistration( ValidationID: validationID, Registered: registered, } - return msg, initialize(msg) + return msg, Initialize(msg) } // ParseSubnetValidatorRegistration parses bytes into an initialized diff --git a/vms/platformvm/warp/message/subnet_validator_weight.go b/vms/platformvm/warp/message/subnet_validator_weight.go index 4091ff9af8ac..22a5b1dcd6e5 100644 --- a/vms/platformvm/warp/message/subnet_validator_weight.go +++ b/vms/platformvm/warp/message/subnet_validator_weight.go @@ -30,7 +30,7 @@ func NewSubnetValidatorWeight( Nonce: nonce, Weight: weight, } - return msg, initialize(msg) + return msg, Initialize(msg) } // ParseSubnetValidatorWeight parses bytes into an initialized diff --git a/wallet/subnet/primary/examples/convert-subnet/main.go b/wallet/subnet/primary/examples/convert-subnet/main.go index 94345c8fe2e8..ef6d751a3a9b 100644 --- a/wallet/subnet/primary/examples/convert-subnet/main.go +++ b/wallet/subnet/primary/examples/convert-subnet/main.go @@ -22,8 +22,8 @@ func main() { key := genesis.EWOQKey uri := "http://localhost:9700" kc := secp256k1fx.NewKeychain(key) - subnetID := ids.FromStringOrPanic("2eZYSgCU738xN7aRw47NsBUPqnKkoqJMYUJexTsX19VdTNSZc9") - chainID := ids.FromStringOrPanic("JCuZD3rg8dEEeY5cJwQxwSj6Shqo8DtRynBCt1nXkBXCouZVw") + subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") + chainID := ids.FromStringOrPanic("21G9Uqg2R7hYa81kZK7oMCgstsHEiNGbkpB9kdBLUeWx3wWMsV") addressHex := "" weight := units.Schmeckle diff --git a/wallet/subnet/primary/examples/create-chain/main.go b/wallet/subnet/primary/examples/create-chain/main.go index 9a4ac92782d5..61526d9cf38a 100644 --- a/wallet/subnet/primary/examples/create-chain/main.go +++ b/wallet/subnet/primary/examples/create-chain/main.go @@ -22,7 +22,7 @@ func main() { key := genesis.EWOQKey uri := primary.LocalAPIURI kc := secp256k1fx.NewKeychain(key) - subnetIDStr := "2eZYSgCU738xN7aRw47NsBUPqnKkoqJMYUJexTsX19VdTNSZc9" + subnetIDStr := "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof" genesis := &xsgenesis.Genesis{ Timestamp: time.Now().Unix(), Allocations: []xsgenesis.Allocation{ diff --git a/wallet/subnet/primary/examples/register-subnet-validator/main.go b/wallet/subnet/primary/examples/register-subnet-validator/main.go index f3841cd2fe4e..41c7f1661a9c 100644 --- a/wallet/subnet/primary/examples/register-subnet-validator/main.go +++ b/wallet/subnet/primary/examples/register-subnet-validator/main.go @@ -6,6 +6,7 @@ package main import ( "context" "encoding/hex" + "encoding/json" "log" "os" "time" @@ -26,10 +27,10 @@ import ( func main() { key := genesis.EWOQKey - uri := "http://localhost:9650" + uri := "http://localhost:9710" kc := secp256k1fx.NewKeychain(key) - subnetID := ids.FromStringOrPanic("2eZYSgCU738xN7aRw47NsBUPqnKkoqJMYUJexTsX19VdTNSZc9") - chainID := ids.FromStringOrPanic("JCuZD3rg8dEEeY5cJwQxwSj6Shqo8DtRynBCt1nXkBXCouZVw") + subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") + chainID := ids.FromStringOrPanic("21G9Uqg2R7hYa81kZK7oMCgstsHEiNGbkpB9kdBLUeWx3wWMsV") addressHex := "" weight := units.Schmeckle @@ -85,6 +86,11 @@ func main() { if err != nil { log.Fatalf("failed to create RegisterSubnetValidator message: %s\n", err) } + addressedCallPayloadJSON, err := json.MarshalIndent(addressedCallPayload, "", "\t") + if err != nil { + log.Fatalf("failed to marshal RegisterSubnetValidator message: %s\n", err) + } + log.Println(string(addressedCallPayloadJSON)) addressedCall, err := payload.NewAddressedCall( address, diff --git a/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go b/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go new file mode 100644 index 000000000000..8ba1bc80a098 --- /dev/null +++ b/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go @@ -0,0 +1,190 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "encoding/json" + "log" + "net/netip" + "time" + + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/protobuf/proto" + + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/peer" + "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/snow/networking/router" + "github.com/ava-labs/avalanchego/utils/compression" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + + p2pmessage "github.com/ava-labs/avalanchego/message" + warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" +) + +var registerSubnetValidatorJSON = []byte(`{ + "subnetID": "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof", + "nodeID": "NodeID-9Nm8NNALws11M5J89rXgc46u6L52e7fmz", + "weight": 49463, + "blsPublicKey": [ + 138, + 25, + 41, + 204, + 242, + 137, + 56, + 208, + 15, + 227, + 216, + 136, + 137, + 144, + 59, + 120, + 205, + 181, + 230, + 232, + 225, + 24, + 80, + 117, + 225, + 16, + 102, + 93, + 83, + 184, + 245, + 240, + 232, + 97, + 238, + 49, + 217, + 4, + 131, + 121, + 19, + 213, + 202, + 233, + 38, + 19, + 70, + 58 + ], + "expiry": 1727548306 +}`) + +func main() { + uri := primary.LocalAPIURI + infoClient := info.NewClient(uri) + networkID, err := infoClient.GetNetworkID(context.Background()) + if err != nil { + log.Fatalf("failed to fetch network ID: %s\n", err) + } + + var registerSubnetValidator warpmessage.RegisterSubnetValidator + err = json.Unmarshal(registerSubnetValidatorJSON, ®isterSubnetValidator) + if err != nil { + log.Fatalf("failed to unmarshal RegisterSubnetValidator message: %s\n", err) + } + err = warpmessage.Initialize(®isterSubnetValidator) + if err != nil { + log.Fatalf("failed to initialize RegisterSubnetValidator message: %s\n", err) + } + + var validationID ids.ID = hashing.ComputeHash256Array(registerSubnetValidator.Bytes()) + subnetValidatorRegistration, err := warpmessage.NewSubnetValidatorRegistration( + validationID, + true, + ) + if err != nil { + log.Fatalf("failed to create SubnetValidatorRegistration message: %s\n", err) + } + + addressedCall, err := payload.NewAddressedCall( + nil, + subnetValidatorRegistration.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create AddressedCall message: %s\n", err) + } + + unsignedWarp, err := warp.NewUnsignedMessage( + networkID, + constants.PlatformChainID, + addressedCall.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create unsigned Warp message: %s\n", err) + } + + p, err := peer.StartTestPeer( + context.Background(), + netip.AddrPortFrom( + netip.AddrFrom4([4]byte{127, 0, 0, 1}), + 9651, + ), + networkID, + router.InboundHandlerFunc(func(_ context.Context, msg p2pmessage.InboundMessage) { + log.Printf("received %s: %s", msg.Op(), msg.Message()) + }), + ) + if err != nil { + log.Fatalf("failed to start peer: %s\n", err) + } + + mesageBuilder, err := p2pmessage.NewCreator( + logging.NoLog{}, + prometheus.NewRegistry(), + compression.TypeZstd, + time.Hour, + ) + if err != nil { + log.Fatalf("failed to create message builder: %s\n", err) + } + + appRequestPayload, err := proto.Marshal(&sdk.SignatureRequest{ + Message: unsignedWarp.Bytes(), + Justification: registerSubnetValidator.Bytes(), + }) + if err != nil { + log.Fatalf("failed to marshal SignatureRequest: %s\n", err) + } + + appRequest, err := mesageBuilder.AppRequest( + constants.PlatformChainID, + 0, + time.Hour, + p2p.PrefixMessage( + p2p.ProtocolPrefix(p2p.SignatureRequestHandlerID), + appRequestPayload, + ), + ) + if err != nil { + log.Fatalf("failed to create AppRequest: %s\n", err) + } + + p.Send(context.Background(), appRequest) + + time.Sleep(5 * time.Second) + + p.StartClose() + err = p.AwaitClosed(context.Background()) + if err != nil { + log.Fatalf("failed to close peer: %s\n", err) + } +} From 1851f2b0b25a010cd5d2dc40e76f5ee9d9e17fe0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 28 Sep 2024 15:56:08 -0400 Subject: [PATCH 083/199] remove comment --- vms/platformvm/network/warp.go | 1 - 1 file changed, 1 deletion(-) diff --git a/vms/platformvm/network/warp.go b/vms/platformvm/network/warp.go index 0cb9e945d454..6147fdb2a1f6 100644 --- a/vms/platformvm/network/warp.go +++ b/vms/platformvm/network/warp.go @@ -54,7 +54,6 @@ type signatureRequestHandler struct { signer warp.Signer } -// TODO: This should be allowed only for local networks func (s signatureRequestHandler) AppRequest( _ context.Context, _ ids.NodeID, From d881881040bf6fa3a283f53d6afea0de98a17e4c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 30 Sep 2024 13:25:23 -0400 Subject: [PATCH 084/199] Refund balance --- .../txs/executor/standard_tx_executor.go | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 87849ae19fbb..26cf72b40bc5 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -22,6 +22,7 @@ import ( "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/ava-labs/avalanchego/vms/platformvm/fx" "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" @@ -834,16 +835,54 @@ func (e *StandardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat return fmt.Errorf("expected address %s but got %s", expectedAddress, addressedCall.SourceAddress) } - // If the nonce calculation overflows, weight must be 0, so the validator is - // being removed anyways. + txID := e.Tx.ID() + if msg.Weight == 0 && sov.EndAccumulatedFee != 0 { + // If we are removing an active validator, we need to refund the + // remaining balance. + var remainingBalanceOwner fx.Owner + if _, err := txs.Codec.Unmarshal(sov.RemainingBalanceOwner, &remainingBalanceOwner); err != nil { + return err + } + + accruedFees := e.State.GetAccruedFees() + if sov.EndAccumulatedFee <= accruedFees { + // This should never happen as the validator should have been + // evicted. + return fmt.Errorf("validator has insufficient funds to cover accrued fees") + } + remainingBalance := sov.EndAccumulatedFee - accruedFees + + outIntf, err := e.Fx.CreateOutput(remainingBalance, remainingBalanceOwner) + if err != nil { + return fmt.Errorf("failed to create output: %w", err) + } + + out, ok := outIntf.(verify.State) + if !ok { + return ErrInvalidState + } + + utxo := &avax.UTXO{ + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: uint32(len(tx.Outs)), + }, + Asset: avax.Asset{ + ID: e.Ctx.AVAXAssetID, + }, + Out: out, + } + e.State.AddUTXO(utxo) + } + + // If the weight is being set to 0, the validator is being removed and the + // nonce doesn't matter. sov.MinNonce = msg.Nonce + 1 sov.Weight = msg.Weight if err := e.State.PutSubnetOnlyValidator(sov); err != nil { return err } - txID := e.Tx.ID() - // Consume the UTXOS avax.Consume(e.State, tx.Ins) // Produce the UTXOS From 3708a73984ee2a32c0732e00285d98c8f6da8a5d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 30 Sep 2024 18:18:38 -0400 Subject: [PATCH 085/199] ACP-77: Implement IncreaseBalanceTx --- vms/platformvm/metrics/tx_metrics.go | 7 ++ vms/platformvm/txs/codec.go | 1 + .../txs/executor/atomic_tx_executor.go | 4 ++ .../txs/executor/proposal_tx_executor.go | 4 ++ .../txs/executor/standard_tx_executor.go | 67 +++++++++++++++++++ vms/platformvm/txs/executor/warp_verifier.go | 4 ++ vms/platformvm/txs/fee/complexity.go | 19 ++++++ vms/platformvm/txs/fee/static_calculator.go | 4 ++ vms/platformvm/txs/increase_balance_tx.go | 49 ++++++++++++++ vms/platformvm/txs/visitor.go | 1 + wallet/chain/p/builder/builder.go | 60 +++++++++++++++++ .../chain/p/builder/builder_with_options.go | 12 ++++ wallet/chain/p/signer/visitor.go | 8 +++ wallet/chain/p/wallet/backend_visitor.go | 4 ++ wallet/chain/p/wallet/wallet.go | 24 +++++++ wallet/chain/p/wallet/with_options.go | 12 ++++ 16 files changed, 280 insertions(+) create mode 100644 vms/platformvm/txs/increase_balance_tx.go diff --git a/vms/platformvm/metrics/tx_metrics.go b/vms/platformvm/metrics/tx_metrics.go index c194f603f97b..5077b63c15df 100644 --- a/vms/platformvm/metrics/tx_metrics.go +++ b/vms/platformvm/metrics/tx_metrics.go @@ -159,3 +159,10 @@ func (m *txMetrics) SetSubnetValidatorWeightTx(*txs.SetSubnetValidatorWeightTx) }).Inc() return nil } + +func (m *txMetrics) IncreaseBalanceTx(*txs.IncreaseBalanceTx) error { + m.numTxs.With(prometheus.Labels{ + txLabel: "increase_balance", + }).Inc() + return nil +} diff --git a/vms/platformvm/txs/codec.go b/vms/platformvm/txs/codec.go index 51e56e380e99..dbe84b196a1a 100644 --- a/vms/platformvm/txs/codec.go +++ b/vms/platformvm/txs/codec.go @@ -125,5 +125,6 @@ func RegisterEtnaTypes(targetCodec linearcodec.Codec) error { targetCodec.RegisterType(&ConvertSubnetTx{}), targetCodec.RegisterType(&RegisterSubnetValidatorTx{}), targetCodec.RegisterType(&SetSubnetValidatorWeightTx{}), + targetCodec.RegisterType(&IncreaseBalanceTx{}), ) } diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index 7327766b5360..8cbe7b123d04 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -94,6 +94,10 @@ func (*AtomicTxExecutor) SetSubnetValidatorWeightTx(*txs.SetSubnetValidatorWeigh return ErrWrongTxType } +func (*AtomicTxExecutor) IncreaseBalanceTx(*txs.IncreaseBalanceTx) error { + return ErrWrongTxType +} + func (e *AtomicTxExecutor) ImportTx(tx *txs.ImportTx) error { return e.atomicTx(tx) } diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go index 93b2f5aff506..a8a2b443d980 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor.go @@ -111,6 +111,10 @@ func (*ProposalTxExecutor) SetSubnetValidatorWeightTx(*txs.SetSubnetValidatorWei return ErrWrongTxType } +func (*ProposalTxExecutor) IncreaseBalanceTx(*txs.IncreaseBalanceTx) error { + return ErrWrongTxType +} + func (e *ProposalTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { // AddValidatorTx is a proposal transaction until the Banff fork // activation. Following the activation, AddValidatorTxs must be issued into diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index add7f6494462..28d55781635c 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -880,6 +880,73 @@ func (e *StandardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat return nil } +func (e *StandardTxExecutor) IncreaseBalanceTx(tx *txs.IncreaseBalanceTx) error { + var ( + currentTimestamp = e.State.GetTimestamp() + upgrades = e.Backend.Config.UpgradeConfig + ) + if !upgrades.IsEtnaActivated(currentTimestamp) { + return errEtnaUpgradeNotActive + } + + if err := e.Tx.SyntacticVerify(e.Ctx); err != nil { + return err + } + + if err := avax.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil { + return err + } + + // Verify the flowcheck + fee, err := e.FeeCalculator.CalculateFee(tx) + if err != nil { + return err + } + + fee, err = safemath.Add(fee, tx.Balance) + if err != nil { + return err + } + + if err := e.Backend.FlowChecker.VerifySpend( + tx, + e.State, + tx.Ins, + tx.Outs, + e.Tx.Creds, + map[ids.ID]uint64{ + e.Ctx.AVAXAssetID: fee, + }, + ); err != nil { + return err + } + + sov, err := e.State.GetSubnetOnlyValidator(tx.ValidationID) + if err != nil { + return err + } + + if sov.EndAccumulatedFee == 0 { + sov.EndAccumulatedFee = e.State.GetAccruedFees() + } + sov.EndAccumulatedFee, err = safemath.Add(sov.EndAccumulatedFee, tx.Balance) + if err != nil { + return err + } + + if err := e.State.PutSubnetOnlyValidator(sov); err != nil { + return err + } + + txID := e.Tx.ID() + + // Consume the UTXOS + avax.Consume(e.State, tx.Ins) + // Produce the UTXOS + avax.Produce(e.State, txID, tx.Outs) + return nil +} + func (e *StandardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { if err := verifyAddPermissionlessValidatorTx( e.Backend, diff --git a/vms/platformvm/txs/executor/warp_verifier.go b/vms/platformvm/txs/executor/warp_verifier.go index f637d425a4cd..edf05aa61f05 100644 --- a/vms/platformvm/txs/executor/warp_verifier.go +++ b/vms/platformvm/txs/executor/warp_verifier.go @@ -106,6 +106,10 @@ func (*warpVerifier) ConvertSubnetTx(*txs.ConvertSubnetTx) error { return nil } +func (w *warpVerifier) IncreaseBalanceTx(tx *txs.IncreaseBalanceTx) error { + return nil +} + func (w *warpVerifier) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetValidatorTx) error { return w.verify(tx.Message) } diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index a5a746cdbcaf..683bfb5bf631 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -211,6 +211,14 @@ var ( gas.DBWrite: 0, // TODO gas.Compute: 0, } + IntrinsicIncreaseBalanceTxComplexities = gas.Dimensions{ + gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + + ids.IDLen + // validationID + wrappers.LongLen, // balance + gas.DBRead: 0, // TODO + gas.DBWrite: 0, // TODO + gas.Compute: 0, + } errUnsupportedOutput = errors.New("unsupported output type") errUnsupportedInput = errors.New("unsupported input type") @@ -739,6 +747,17 @@ func (c *complexityVisitor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidato return err } +func (c *complexityVisitor) IncreaseBalanceTx(tx *txs.IncreaseBalanceTx) error { + baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) + if err != nil { + return err + } + c.output, err = IntrinsicIncreaseBalanceTxComplexities.Add( + &baseTxComplexity, + ) + return err +} + func baseTxComplexity(tx *txs.BaseTx) (gas.Dimensions, error) { outputsComplexity, err := OutputComplexity(tx.Outs...) if err != nil { diff --git a/vms/platformvm/txs/fee/static_calculator.go b/vms/platformvm/txs/fee/static_calculator.go index 8e58827dcb35..f6b9c84daf55 100644 --- a/vms/platformvm/txs/fee/static_calculator.go +++ b/vms/platformvm/txs/fee/static_calculator.go @@ -59,6 +59,10 @@ func (*staticVisitor) SetSubnetValidatorWeightTx(*txs.SetSubnetValidatorWeightTx return ErrUnsupportedTx } +func (*staticVisitor) IncreaseBalanceTx(*txs.IncreaseBalanceTx) error { + return ErrUnsupportedTx +} + func (c *staticVisitor) AddValidatorTx(*txs.AddValidatorTx) error { c.fee = c.config.AddPrimaryNetworkValidatorFee return nil diff --git a/vms/platformvm/txs/increase_balance_tx.go b/vms/platformvm/txs/increase_balance_tx.go new file mode 100644 index 000000000000..fd2cc25abcb0 --- /dev/null +++ b/vms/platformvm/txs/increase_balance_tx.go @@ -0,0 +1,49 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txs + +import ( + "errors" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" +) + +var ( + _ UnsignedTx = (*IncreaseBalanceTx)(nil) + + ErrZeroBalance = errors.New("balance must be greater than 0") +) + +type IncreaseBalanceTx struct { + // Metadata, inputs and outputs + BaseTx `serialize:"true"` + // ID corresponding to the validator + ValidationID ids.ID `serialize:"true" json:"validationID"` + // Balance <= sum($AVAX inputs) - sum($AVAX outputs) - TxFee + Balance uint64 `serialize:"true" json:"balance"` +} + +func (tx *IncreaseBalanceTx) SyntacticVerify(ctx *snow.Context) error { + switch { + case tx == nil: + return ErrNilTx + case tx.SyntacticallyVerified: + // already passed syntactic verification + return nil + case tx.Balance == 0: + return ErrZeroBalance + } + + if err := tx.BaseTx.SyntacticVerify(ctx); err != nil { + return err + } + + tx.SyntacticallyVerified = true + return nil +} + +func (tx *IncreaseBalanceTx) Visit(visitor Visitor) error { + return visitor.IncreaseBalanceTx(tx) +} diff --git a/vms/platformvm/txs/visitor.go b/vms/platformvm/txs/visitor.go index 6aa766e1e3ea..fbaf2a12124d 100644 --- a/vms/platformvm/txs/visitor.go +++ b/vms/platformvm/txs/visitor.go @@ -30,4 +30,5 @@ type Visitor interface { ConvertSubnetTx(*ConvertSubnetTx) error RegisterSubnetValidatorTx(*RegisterSubnetValidatorTx) error SetSubnetValidatorWeightTx(*SetSubnetValidatorWeightTx) error + IncreaseBalanceTx(*IncreaseBalanceTx) error } diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index e707366416a0..c773a1d3547e 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -192,6 +192,17 @@ type Builder interface { options ...common.Option, ) (*txs.SetSubnetValidatorWeightTx, error) + // NewIncreaseBalanceTx increases the balance of a validator on + // Permissionless L1 for the continuous fee. + // + // - [validationID] of the validator + // - [balance] amount to increase the validator's balance by + NewIncreaseBalanceTx( + validationID ids.ID, + balance uint64, + options ...common.Option, + ) (*txs.IncreaseBalanceTx, error) + // NewImportTx creates an import transaction that attempts to consume all // the available UTXOs and import the funds to [to]. // @@ -1004,6 +1015,55 @@ func (b *builder) NewSetSubnetValidatorWeightTx( return tx, b.initCtx(tx) } +func (b *builder) NewIncreaseBalanceTx( + validationID ids.ID, + balance uint64, + options ...common.Option, +) (*txs.IncreaseBalanceTx, error) { + var ( + toBurn = map[ids.ID]uint64{ + b.context.AVAXAssetID: balance, + } + toStake = map[ids.ID]uint64{} + ops = common.NewOptions(options) + memo = ops.Memo() + memoComplexity = gas.Dimensions{ + gas.Bandwidth: uint64(len(memo)), + } + ) + complexity, err := fee.IntrinsicIncreaseBalanceTxComplexities.Add( + &memoComplexity, + ) + if err != nil { + return nil, err + } + + inputs, outputs, _, err := b.spend( + toBurn, + toStake, + 0, + complexity, + nil, + ops, + ) + if err != nil { + return nil, err + } + + tx := &txs.IncreaseBalanceTx{ + BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ + NetworkID: b.context.NetworkID, + BlockchainID: constants.PlatformChainID, + Ins: inputs, + Outs: outputs, + Memo: memo, + }}, + ValidationID: validationID, + Balance: balance, + } + return tx, b.initCtx(tx) +} + func (b *builder) NewImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/chain/p/builder/builder_with_options.go b/wallet/chain/p/builder/builder_with_options.go index d3a55de058d9..90af14e926b7 100644 --- a/wallet/chain/p/builder/builder_with_options.go +++ b/wallet/chain/p/builder/builder_with_options.go @@ -198,6 +198,18 @@ func (b *builderWithOptions) NewSetSubnetValidatorWeightTx( ) } +func (b *builderWithOptions) NewIncreaseBalanceTx( + validationID ids.ID, + balance uint64, + options ...common.Option, +) (*txs.IncreaseBalanceTx, error) { + return b.builder.NewIncreaseBalanceTx( + validationID, + balance, + common.UnionOptions(b.options, options)..., + ) +} + func (b *builderWithOptions) NewImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/chain/p/signer/visitor.go b/wallet/chain/p/signer/visitor.go index 8a6f1bf0e782..a4fc2aa94e8a 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -185,6 +185,14 @@ func (s *visitor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidatorWeightTx) return sign(s.tx, true, txSigners) } +func (s *visitor) IncreaseBalanceTx(tx *txs.IncreaseBalanceTx) error { + txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) + if err != nil { + return err + } + return sign(s.tx, true, txSigners) +} + func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { diff --git a/wallet/chain/p/wallet/backend_visitor.go b/wallet/chain/p/wallet/backend_visitor.go index db0b544d4f88..a1e399d6415f 100644 --- a/wallet/chain/p/wallet/backend_visitor.go +++ b/wallet/chain/p/wallet/backend_visitor.go @@ -82,6 +82,10 @@ func (b *backendVisitor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidatorWe return b.baseTx(&tx.BaseTx) } +func (b *backendVisitor) IncreaseBalanceTx(tx *txs.IncreaseBalanceTx) error { + return b.baseTx(&tx.BaseTx) +} + func (b *backendVisitor) BaseTx(tx *txs.BaseTx) error { return b.baseTx(tx) } diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index bf0a614a0eaf..c124550e235e 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -179,6 +179,18 @@ type Wallet interface { options ...common.Option, ) (*txs.Tx, error) + // IssueIncreaseBalanceTx creates, signs, and issues a transaction that + // increases the balance of a validator on Permissionless L1 for the + // continuous fee. + // + // - [validationID] of the validator + // - [balance] amount to increase the validator's balance by + IssueIncreaseBalanceTx( + validationID ids.ID, + balance uint64, + options ...common.Option, + ) (*txs.Tx, error) + // IssueImportTx creates, signs, and issues an import transaction that // attempts to consume all the available UTXOs and import the funds to [to]. // @@ -458,6 +470,18 @@ func (w *wallet) IssueSetSubnetValidatorWeightTx( return w.IssueUnsignedTx(utx, options...) } +func (w *wallet) IssueIncreaseBalanceTx( + validationID ids.ID, + balance uint64, + options ...common.Option, +) (*txs.Tx, error) { + utx, err := w.builder.NewIncreaseBalanceTx(validationID, balance, options...) + if err != nil { + return nil, err + } + return w.IssueUnsignedTx(utx, options...) +} + func (w *wallet) IssueImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/chain/p/wallet/with_options.go b/wallet/chain/p/wallet/with_options.go index 2beb789ebfdf..42f1ee4cd6a0 100644 --- a/wallet/chain/p/wallet/with_options.go +++ b/wallet/chain/p/wallet/with_options.go @@ -186,6 +186,18 @@ func (w *withOptions) IssueSetSubnetValidatorWeightTx( ) } +func (w *withOptions) IssueIncreaseBalanceTx( + validationID ids.ID, + balance uint64, + options ...common.Option, +) (*txs.Tx, error) { + return w.wallet.IssueIncreaseBalanceTx( + validationID, + balance, + common.UnionOptions(w.options, options)..., + ) +} + func (w *withOptions) IssueImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, From 8fe987cf89f72411161ad06feffd1740016b98d2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 30 Sep 2024 19:20:51 -0400 Subject: [PATCH 086/199] ACP-77: Update warp messages to follow new specification --- vms/platformvm/warp/message/codec.go | 1 - vms/platformvm/warp/message/payload_test.go | 30 +----------- .../warp/message/register_subnet_validator.go | 8 ++-- .../message/set_subnet_validator_weight.go | 48 ------------------- .../set_subnet_validator_weight_test.go | 28 ----------- .../warp/message/subnet_validator_weight.go | 9 +++- 6 files changed, 12 insertions(+), 112 deletions(-) delete mode 100644 vms/platformvm/warp/message/set_subnet_validator_weight.go delete mode 100644 vms/platformvm/warp/message/set_subnet_validator_weight_test.go diff --git a/vms/platformvm/warp/message/codec.go b/vms/platformvm/warp/message/codec.go index b5ade8de30a9..4dda85a1c76e 100644 --- a/vms/platformvm/warp/message/codec.go +++ b/vms/platformvm/warp/message/codec.go @@ -23,7 +23,6 @@ func init() { lc.RegisterType(&SubnetConversion{}), lc.RegisterType(&RegisterSubnetValidator{}), lc.RegisterType(&SubnetValidatorRegistration{}), - lc.RegisterType(&SetSubnetValidatorWeight{}), lc.RegisterType(&SubnetValidatorWeight{}), Codec.RegisterCodec(CodecVersion, lc), ) diff --git a/vms/platformvm/warp/message/payload_test.go b/vms/platformvm/warp/message/payload_test.go index 802f84e01e85..4034d7ba7a3f 100644 --- a/vms/platformvm/warp/message/payload_test.go +++ b/vms/platformvm/warp/message/payload_test.go @@ -128,41 +128,13 @@ func TestParse(t *testing.T) { false, )), }, - { - name: "SetSubnetValidatorWeight", - bytes: []byte{ - // Codec version: - 0x00, 0x00, - // Payload type = SetSubnetValidatorWeight: - 0x00, 0x00, 0x00, 0x03, - // ValidationID: - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, - // Nonce: - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - // Weight: - 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, - }, - expected: mustCreate(NewSetSubnetValidatorWeight( - ids.ID{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, - }, - 0x2122232425262728, - 0x292a2b2c2d2e2f30, - )), - }, { name: "SubnetValidatorWeight", bytes: []byte{ // Codec version: 0x00, 0x00, // Payload type = SubnetValidatorWeight: - 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x03, // ValidationID: 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, diff --git a/vms/platformvm/warp/message/register_subnet_validator.go b/vms/platformvm/warp/message/register_subnet_validator.go index 4a6bd9240435..be8e0495ac4f 100644 --- a/vms/platformvm/warp/message/register_subnet_validator.go +++ b/vms/platformvm/warp/message/register_subnet_validator.go @@ -8,15 +8,15 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/vms/types" ) // RegisterSubnetValidator adds a validator to the subnet. type RegisterSubnetValidator struct { payload - SubnetID ids.ID `serialize:"true" json:"subnetID"` - // TODO: Use a 32-byte nodeID here - NodeID ids.NodeID `serialize:"true" json:"nodeID"` + SubnetID ids.ID `serialize:"true" json:"subnetID"` + NodeID types.JSONByteSlice `serialize:"true" json:"nodeID"` Weight uint64 `serialize:"true" json:"weight"` BLSPublicKey [bls.PublicKeyLen]byte `serialize:"true" json:"blsPublicKey"` Expiry uint64 `serialize:"true" json:"expiry"` @@ -32,7 +32,7 @@ func NewRegisterSubnetValidator( ) (*RegisterSubnetValidator, error) { msg := &RegisterSubnetValidator{ SubnetID: subnetID, - NodeID: nodeID, + NodeID: nodeID[:], Weight: weight, BLSPublicKey: blsPublicKey, Expiry: expiry, diff --git a/vms/platformvm/warp/message/set_subnet_validator_weight.go b/vms/platformvm/warp/message/set_subnet_validator_weight.go deleted file mode 100644 index bde19f7ae67a..000000000000 --- a/vms/platformvm/warp/message/set_subnet_validator_weight.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package message - -import ( - "fmt" - - "github.com/ava-labs/avalanchego/ids" -) - -// SetSubnetValidatorWeight updates the weight of the specified validator. -type SetSubnetValidatorWeight struct { - payload - - ValidationID ids.ID `serialize:"true" json:"validationID"` - Nonce uint64 `serialize:"true" json:"nonce"` - Weight uint64 `serialize:"true" json:"weight"` -} - -// NewSetSubnetValidatorWeight creates a new initialized -// SetSubnetValidatorWeight. -func NewSetSubnetValidatorWeight( - validationID ids.ID, - nonce uint64, - weight uint64, -) (*SetSubnetValidatorWeight, error) { - msg := &SetSubnetValidatorWeight{ - ValidationID: validationID, - Nonce: nonce, - Weight: weight, - } - return msg, initialize(msg) -} - -// ParseSetSubnetValidatorWeight parses bytes into an initialized -// SetSubnetValidatorWeight. -func ParseSetSubnetValidatorWeight(b []byte) (*SetSubnetValidatorWeight, error) { - payloadIntf, err := Parse(b) - if err != nil { - return nil, err - } - payload, ok := payloadIntf.(*SetSubnetValidatorWeight) - if !ok { - return nil, fmt.Errorf("%w: %T", ErrWrongType, payloadIntf) - } - return payload, nil -} diff --git a/vms/platformvm/warp/message/set_subnet_validator_weight_test.go b/vms/platformvm/warp/message/set_subnet_validator_weight_test.go deleted file mode 100644 index 9e54cf2e34b2..000000000000 --- a/vms/platformvm/warp/message/set_subnet_validator_weight_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package message - -import ( - "math/rand" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/ava-labs/avalanchego/ids" -) - -func TestSetSubnetValidatorWeight(t *testing.T) { - require := require.New(t) - - msg, err := NewSetSubnetValidatorWeight( - ids.GenerateTestID(), - rand.Uint64(), //#nosec G404 - rand.Uint64(), //#nosec G404 - ) - require.NoError(err) - - parsed, err := ParseSetSubnetValidatorWeight(msg.Bytes()) - require.NoError(err) - require.Equal(msg, parsed) -} diff --git a/vms/platformvm/warp/message/subnet_validator_weight.go b/vms/platformvm/warp/message/subnet_validator_weight.go index 4091ff9af8ac..eb5132a64874 100644 --- a/vms/platformvm/warp/message/subnet_validator_weight.go +++ b/vms/platformvm/warp/message/subnet_validator_weight.go @@ -9,8 +9,13 @@ import ( "github.com/ava-labs/avalanchego/ids" ) -// SubnetValidatorWeight reports the current nonce and weight of a validator -// registered on the P-chain. +// SubnetValidatorWeight is both received and sent by the P-chain. +// +// If the P-chain is receiving this message, it is treated as a command to +// update the weight of the validator. +// +// If the P-chain is sending this message, it is reporting the current nonce and +// weight of the validator. type SubnetValidatorWeight struct { payload From 30efc6d68fdda75f46c87c652bd5ca1d5d1235f5 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 30 Sep 2024 21:00:38 -0400 Subject: [PATCH 087/199] ACP-77: Add warp message helpers to follow new specification --- vms/platformvm/warp/message/payload_test.go | 52 +++++- .../warp/message/register_subnet_validator.go | 84 +++++++-- .../message/register_subnet_validator_test.go | 161 +++++++++++++++++- .../warp/message/subnet_conversion.go | 32 +++- .../warp/message/subnet_conversion_test.go | 82 +++++++++ .../warp/message/subnet_validator_weight.go | 11 ++ .../message/subnet_validator_weight_test.go | 38 +++++ 7 files changed, 438 insertions(+), 22 deletions(-) diff --git a/vms/platformvm/warp/message/payload_test.go b/vms/platformvm/warp/message/payload_test.go index 4034d7ba7a3f..dc2013f34826 100644 --- a/vms/platformvm/warp/message/payload_test.go +++ b/vms/platformvm/warp/message/payload_test.go @@ -63,21 +63,39 @@ func TestParse(t *testing.T) { 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + // NodeID Length: + 0x00, 0x00, 0x00, 0x14, // NodeID: 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, - // Weight: - 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, // BLSPublicKey: + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, - 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, // Expiry: - 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + // Remaining Balance Owner Threshold: + 0x6d, 0x6e, 0x6f, 0x70, + // Remaining Balance Owner Addresses Length: + 0x00, 0x00, 0x00, 0x01, + // Remaining Balance Owner Address[0]: + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, + 0x81, 0x82, 0x83, 0x84, + // Disable Owner Threshold: + 0x85, 0x86, 0x87, 0x88, + // Disable Owner Addresses Length: + 0x00, 0x00, 0x00, 0x01, + // Disable Owner Address[0]: + 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, + 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, + // Weight: + 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, }, expected: mustCreate(NewRegisterSubnetValidator( ids.ID{ @@ -91,16 +109,36 @@ func TestParse(t *testing.T) { 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, }, - 0x35363738393a3b3c, [bls.PublicKeyLen]byte{ + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, - 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, }, - 0x6d6e6f7071727374, + 0x65666768696a6b6c, + PChainOwner{ + Threshold: 0x6d6e6f70, + Addresses: []ids.ShortID{ + { + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, + 0x81, 0x82, 0x83, 0x84, + }, + }, + }, + PChainOwner{ + Threshold: 0x85868788, + Addresses: []ids.ShortID{ + { + 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, + 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, + }, + }, + }, + 0x9d9e9fa0a1a2a3a4, )), }, { diff --git a/vms/platformvm/warp/message/register_subnet_validator.go b/vms/platformvm/warp/message/register_subnet_validator.go index be8e0495ac4f..cf0b1cbcd569 100644 --- a/vms/platformvm/warp/message/register_subnet_validator.go +++ b/vms/platformvm/warp/message/register_subnet_validator.go @@ -4,38 +4,100 @@ package message import ( + "errors" "fmt" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/vms/types" ) +var ( + ErrInvalidSubnetID = errors.New("invalid subnet ID") + ErrInvalidWeight = errors.New("invalid weight") + ErrInvalidNodeID = errors.New("invalid node ID") + ErrInvalidOwner = errors.New("invalid owner") +) + +type PChainOwner struct { + // The threshold number of `Addresses` that must provide a signature in + // order for the `PChainOwner` to be considered valid. + Threshold uint32 `serialize:"true" json:"threshold"` + // The addresses that are allowed to sign to authenticate a `PChainOwner`. + Addresses []ids.ShortID `serialize:"true" json:"addresses"` +} + // RegisterSubnetValidator adds a validator to the subnet. type RegisterSubnetValidator struct { payload - SubnetID ids.ID `serialize:"true" json:"subnetID"` - NodeID types.JSONByteSlice `serialize:"true" json:"nodeID"` - Weight uint64 `serialize:"true" json:"weight"` - BLSPublicKey [bls.PublicKeyLen]byte `serialize:"true" json:"blsPublicKey"` - Expiry uint64 `serialize:"true" json:"expiry"` + SubnetID ids.ID `serialize:"true" json:"subnetID"` + NodeID types.JSONByteSlice `serialize:"true" json:"nodeID"` + BLSPublicKey [bls.PublicKeyLen]byte `serialize:"true" json:"blsPublicKey"` + Expiry uint64 `serialize:"true" json:"expiry"` + RemainingBalanceOwner PChainOwner `serialize:"true" json:"remainingBalanceOwner"` + DisableOwner PChainOwner `serialize:"true" json:"disableOwner"` + Weight uint64 `serialize:"true" json:"weight"` +} + +func (r *RegisterSubnetValidator) Verify() error { + if r.SubnetID == constants.PrimaryNetworkID { + return ErrInvalidSubnetID + } + if r.Weight == 0 { + return ErrInvalidWeight + } + + nodeID, err := ids.ToNodeID(r.NodeID) + if err != nil { + return fmt.Errorf("%w: %w", ErrInvalidNodeID, err) + } + if nodeID == ids.EmptyNodeID { + return fmt.Errorf("%w: empty nodeID is disallowed", ErrInvalidNodeID) + } + + err = verify.All( + &secp256k1fx.OutputOwners{ + Threshold: r.RemainingBalanceOwner.Threshold, + Addrs: r.RemainingBalanceOwner.Addresses, + }, + &secp256k1fx.OutputOwners{ + Threshold: r.DisableOwner.Threshold, + Addrs: r.DisableOwner.Addresses, + }, + ) + if err != nil { + return fmt.Errorf("%w: %w", ErrInvalidOwner, err) + } + return nil +} + +func (r *RegisterSubnetValidator) ValidationID() ids.ID { + return hashing.ComputeHash256Array(r.Bytes()) } // NewRegisterSubnetValidator creates a new initialized RegisterSubnetValidator. func NewRegisterSubnetValidator( subnetID ids.ID, nodeID ids.NodeID, - weight uint64, blsPublicKey [bls.PublicKeyLen]byte, expiry uint64, + remainingBalanceOwner PChainOwner, + disableOwner PChainOwner, + weight uint64, ) (*RegisterSubnetValidator, error) { msg := &RegisterSubnetValidator{ - SubnetID: subnetID, - NodeID: nodeID[:], - Weight: weight, - BLSPublicKey: blsPublicKey, - Expiry: expiry, + SubnetID: subnetID, + NodeID: nodeID[:], + BLSPublicKey: blsPublicKey, + Expiry: expiry, + RemainingBalanceOwner: remainingBalanceOwner, + DisableOwner: disableOwner, + Weight: weight, } return msg, initialize(msg) } diff --git a/vms/platformvm/warp/message/register_subnet_validator_test.go b/vms/platformvm/warp/message/register_subnet_validator_test.go index 641569ae6e61..68898e47979f 100644 --- a/vms/platformvm/warp/message/register_subnet_validator_test.go +++ b/vms/platformvm/warp/message/register_subnet_validator_test.go @@ -10,7 +10,9 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/hashing" ) func newBLSPublicKey(t *testing.T) [bls.PublicKeyLen]byte { @@ -28,13 +30,168 @@ func TestRegisterSubnetValidator(t *testing.T) { msg, err := NewRegisterSubnetValidator( ids.GenerateTestID(), ids.GenerateTestNodeID(), - rand.Uint64(), //#nosec G404 newBLSPublicKey(t), rand.Uint64(), //#nosec G404 + PChainOwner{ + Threshold: rand.Uint32(), //#nosec G404 + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + PChainOwner{ + Threshold: rand.Uint32(), //#nosec G404 + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + rand.Uint64(), //#nosec G404 ) require.NoError(err) - parsed, err := ParseRegisterSubnetValidator(msg.Bytes()) + bytes := msg.Bytes() + var expectedValidationID ids.ID = hashing.ComputeHash256Array(bytes) + require.Equal(expectedValidationID, msg.ValidationID()) + + parsed, err := ParseRegisterSubnetValidator(bytes) require.NoError(err) require.Equal(msg, parsed) } + +func TestRegisterSubnetValidator_Verify(t *testing.T) { + mustCreate := func(msg *RegisterSubnetValidator, err error) *RegisterSubnetValidator { + require.NoError(t, err) + return msg + } + tests := []struct { + name string + msg *RegisterSubnetValidator + expected error + }{ + { + name: "PrimaryNetworkID", + msg: mustCreate(NewRegisterSubnetValidator( + constants.PrimaryNetworkID, + ids.GenerateTestNodeID(), + newBLSPublicKey(t), + rand.Uint64(), //#nosec G404 + PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + PChainOwner{ + Threshold: 0, + }, + 1, + )), + expected: ErrInvalidSubnetID, + }, + { + name: "Weight = 0", + msg: mustCreate(NewRegisterSubnetValidator( + ids.GenerateTestID(), + ids.GenerateTestNodeID(), + newBLSPublicKey(t), + rand.Uint64(), //#nosec G404 + PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + PChainOwner{ + Threshold: 0, + }, + 0, + )), + expected: ErrInvalidWeight, + }, + { + name: "Invalid NodeID Length", + msg: &RegisterSubnetValidator{ + SubnetID: ids.GenerateTestID(), + NodeID: nil, + BLSPublicKey: newBLSPublicKey(t), + Expiry: rand.Uint64(), //#nosec G404 + RemainingBalanceOwner: PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + DisableOwner: PChainOwner{ + Threshold: 0, + }, + Weight: 1, + }, + expected: ErrInvalidNodeID, + }, + { + name: "Invalid NodeID", + msg: mustCreate(NewRegisterSubnetValidator( + ids.GenerateTestID(), + ids.EmptyNodeID, + newBLSPublicKey(t), + rand.Uint64(), //#nosec G404 + PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + PChainOwner{ + Threshold: 0, + }, + 1, + )), + expected: ErrInvalidNodeID, + }, + { + name: "Invalid Owner", + msg: mustCreate(NewRegisterSubnetValidator( + ids.GenerateTestID(), + ids.GenerateTestNodeID(), + newBLSPublicKey(t), + rand.Uint64(), //#nosec G404 + PChainOwner{ + Threshold: 0, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + PChainOwner{ + Threshold: 0, + }, + 1, + )), + expected: ErrInvalidOwner, + }, + { + name: "Valid", + msg: mustCreate(NewRegisterSubnetValidator( + ids.GenerateTestID(), + ids.GenerateTestNodeID(), + newBLSPublicKey(t), + rand.Uint64(), //#nosec G404 + PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + PChainOwner{ + Threshold: 0, + }, + 1, + )), + expected: nil, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.msg.Verify() + require.ErrorIs(t, err, test.expected) + }) + } +} diff --git a/vms/platformvm/warp/message/subnet_conversion.go b/vms/platformvm/warp/message/subnet_conversion.go index d4ca5587453d..a93354fbe446 100644 --- a/vms/platformvm/warp/message/subnet_conversion.go +++ b/vms/platformvm/warp/message/subnet_conversion.go @@ -7,13 +7,41 @@ import ( "fmt" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/vms/types" ) -// SubnetConversion reports summary of the subnet conversation that occurred on -// the P-chain. +type SubnetConversionValidatorData struct { + NodeID types.JSONByteSlice `serialize:"true" json:"nodeID"` + BLSPublicKey [bls.PublicKeyLen]byte `serialize:"true" json:"blsPublicKey"` + Weight uint64 `serialize:"true" json:"weight"` +} + +type SubnetConversionData struct { + SubnetID ids.ID `serialize:"true" json:"subnetID"` + ManagerChainID ids.ID `serialize:"true" json:"managerChainID"` + ManagerAddress types.JSONByteSlice `serialize:"true" json:"managerAddress"` + Validators []SubnetConversionValidatorData `serialize:"true" json:"validators"` +} + +// SubnetConversionID creates a subnet conversion ID from the provided subnet +// conversion data. +func SubnetConversionID(data SubnetConversionData) (ids.ID, error) { + bytes, err := Codec.Marshal(CodecVersion, &data) + if err != nil { + return ids.Empty, err + } + return hashing.ComputeHash256Array(bytes), nil +} + +// SubnetConversion reports the summary of the subnet conversation that occurred +// on the P-chain. type SubnetConversion struct { payload + // ID of the subnet conversion. It is typically generated by calling + // SubnetConversionID. ID ids.ID `serialize:"true" json:"id"` } diff --git a/vms/platformvm/warp/message/subnet_conversion_test.go b/vms/platformvm/warp/message/subnet_conversion_test.go index ad823291eb09..1da07f75e344 100644 --- a/vms/platformvm/warp/message/subnet_conversion_test.go +++ b/vms/platformvm/warp/message/subnet_conversion_test.go @@ -9,8 +9,90 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/vms/types" ) +func TestSubnetConversionID(t *testing.T) { + require := require.New(t) + + subnetConversionDataBytes := []byte{ + // Codec version: + 0x00, 0x00, + // SubnetID: + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + // ManagerChainID: + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, + // ManagerAddress Length: + 0x00, 0x00, 0x00, 0x01, + // ManagerAddress: + 0x41, + // Validators Length: + 0x00, 0x00, 0x00, 0x01, + // Validator[0]: + // NodeID Length: + 0x00, 0x00, 0x00, 0x14, + // NodeID: + 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, + 0x52, 0x53, 0x54, 0x55, + // BLSPublicKey: + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, + 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, + 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, + // Weight: + 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, + } + var expectedSubnetConversionID ids.ID = hashing.ComputeHash256Array(subnetConversionDataBytes) + + subnetConversionData := SubnetConversionData{ + SubnetID: ids.ID{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + }, + ManagerChainID: ids.ID{ + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, + }, + ManagerAddress: []byte{0x41}, + Validators: []SubnetConversionValidatorData{ + { + NodeID: types.JSONByteSlice([]byte{ + 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, + 0x52, 0x53, 0x54, 0x55, + }), + BLSPublicKey: [bls.PublicKeyLen]byte{ + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, + 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, + 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, + }, + Weight: 0x868788898a8b8c8d, + }, + }, + } + subnetConversionID, err := SubnetConversionID(subnetConversionData) + require.NoError(err) + require.Equal(expectedSubnetConversionID, subnetConversionID) +} + func TestSubnetConversion(t *testing.T) { require := require.New(t) diff --git a/vms/platformvm/warp/message/subnet_validator_weight.go b/vms/platformvm/warp/message/subnet_validator_weight.go index eb5132a64874..dcfa6c5a16c0 100644 --- a/vms/platformvm/warp/message/subnet_validator_weight.go +++ b/vms/platformvm/warp/message/subnet_validator_weight.go @@ -4,11 +4,15 @@ package message import ( + "errors" "fmt" + "math" "github.com/ava-labs/avalanchego/ids" ) +var ErrNonceReservedForRemoval = errors.New("maxUint64 nonce is reserved for removal") + // SubnetValidatorWeight is both received and sent by the P-chain. // // If the P-chain is receiving this message, it is treated as a command to @@ -24,6 +28,13 @@ type SubnetValidatorWeight struct { Weight uint64 `serialize:"true" json:"weight"` } +func (s *SubnetValidatorWeight) Verify() error { + if s.Nonce == math.MaxUint64 && s.Weight != 0 { + return ErrNonceReservedForRemoval + } + return nil +} + // NewSubnetValidatorWeight creates a new initialized SubnetValidatorWeight. func NewSubnetValidatorWeight( validationID ids.ID, diff --git a/vms/platformvm/warp/message/subnet_validator_weight_test.go b/vms/platformvm/warp/message/subnet_validator_weight_test.go index faa67ae5c384..17cef30b88cb 100644 --- a/vms/platformvm/warp/message/subnet_validator_weight_test.go +++ b/vms/platformvm/warp/message/subnet_validator_weight_test.go @@ -4,6 +4,7 @@ package message import ( + "math" "math/rand" "testing" @@ -26,3 +27,40 @@ func TestSubnetValidatorWeight(t *testing.T) { require.NoError(err) require.Equal(msg, parsed) } + +func TestSubnetValidatorWeight_Verify(t *testing.T) { + mustCreate := func(msg *SubnetValidatorWeight, err error) *SubnetValidatorWeight { + require.NoError(t, err) + return msg + } + tests := []struct { + name string + msg *SubnetValidatorWeight + expected error + }{ + { + name: "Invalid Nonce", + msg: mustCreate(NewSubnetValidatorWeight( + ids.GenerateTestID(), + math.MaxUint64, + 1, + )), + expected: ErrNonceReservedForRemoval, + }, + { + name: "Valid", + msg: mustCreate(NewSubnetValidatorWeight( + ids.GenerateTestID(), + math.MaxUint64, + 0, + )), + expected: nil, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.msg.Verify() + require.ErrorIs(t, err, test.expected) + }) + } +} From 903f7c3e9b62ac2b46ee7fb7eaade6bc92b94a14 Mon Sep 17 00:00:00 2001 From: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:43:14 -0400 Subject: [PATCH 088/199] migrate x/sync to p2p Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- network/p2p/p2ptest/client.go | 24 ++--- network/p2p/p2ptest/client_test.go | 4 +- x/sync/client_test.go | 19 +--- x/sync/manager.go | 23 ++--- x/sync/network_server.go | 75 +++++++------- x/sync/network_server_test.go | 21 ++-- x/sync/sync_test.go | 152 +++++++++++++++-------------- 7 files changed, 155 insertions(+), 163 deletions(-) diff --git a/network/p2p/p2ptest/client.go b/network/p2p/p2ptest/client.go index b75654028666..747904b40ffb 100644 --- a/network/p2p/p2ptest/client.go +++ b/network/p2p/p2ptest/client.go @@ -21,25 +21,19 @@ import ( // NewClient generates a client-server pair and returns the client used to // communicate with a server with the specified handler -func NewClient( - t *testing.T, - ctx context.Context, - handler p2p.Handler, - clientNodeID ids.NodeID, - serverNodeID ids.NodeID, -) *p2p.Client { +func NewClient(t *testing.T, rootCtx context.Context, handler p2p.Handler) *p2p.Client { clientSender := &enginetest.Sender{} serverSender := &enginetest.Sender{} + clientNodeID := ids.GenerateTestNodeID() clientNetwork, err := p2p.NewNetwork(logging.NoLog{}, clientSender, prometheus.NewRegistry(), "") require.NoError(t, err) + serverNodeID := ids.GenerateTestNodeID() serverNetwork, err := p2p.NewNetwork(logging.NoLog{}, serverSender, prometheus.NewRegistry(), "") require.NoError(t, err) clientSender.SendAppGossipF = func(ctx context.Context, _ common.SendConfig, gossipBytes []byte) error { - // Send the request asynchronously to avoid deadlock when the server - // sends the response back to the client go func() { require.NoError(t, serverNetwork.AppGossip(ctx, clientNodeID, gossipBytes)) }() @@ -58,8 +52,6 @@ func NewClient( } serverSender.SendAppResponseF = func(ctx context.Context, _ ids.NodeID, requestID uint32, responseBytes []byte) error { - // Send the request asynchronously to avoid deadlock when the server - // sends the response back to the client go func() { require.NoError(t, clientNetwork.AppResponse(ctx, serverNodeID, requestID, responseBytes)) }() @@ -68,8 +60,6 @@ func NewClient( } serverSender.SendAppErrorF = func(ctx context.Context, _ ids.NodeID, requestID uint32, errorCode int32, errorMessage string) error { - // Send the request asynchronously to avoid deadlock when the server - // sends the response back to the client go func() { require.NoError(t, clientNetwork.AppRequestFailed(ctx, serverNodeID, requestID, &common.AppError{ Code: errorCode, @@ -80,10 +70,10 @@ func NewClient( return nil } - require.NoError(t, clientNetwork.Connected(ctx, clientNodeID, nil)) - require.NoError(t, clientNetwork.Connected(ctx, serverNodeID, nil)) - require.NoError(t, serverNetwork.Connected(ctx, clientNodeID, nil)) - require.NoError(t, serverNetwork.Connected(ctx, serverNodeID, nil)) + require.NoError(t, clientNetwork.Connected(rootCtx, clientNodeID, nil)) + require.NoError(t, clientNetwork.Connected(rootCtx, serverNodeID, nil)) + require.NoError(t, serverNetwork.Connected(rootCtx, clientNodeID, nil)) + require.NoError(t, serverNetwork.Connected(rootCtx, serverNodeID, nil)) require.NoError(t, serverNetwork.AddHandler(0, handler)) return clientNetwork.NewClient(0) diff --git a/network/p2p/p2ptest/client_test.go b/network/p2p/p2ptest/client_test.go index 45ae970ecf0f..cef624aaccbc 100644 --- a/network/p2p/p2ptest/client_test.go +++ b/network/p2p/p2ptest/client_test.go @@ -27,7 +27,7 @@ func TestNewClient_AppGossip(t *testing.T) { }, } - client := NewClient(t, ctx, testHandler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + client := NewClient(t, ctx, testHandler) require.NoError(client.AppGossip(ctx, common.SendConfig{}, []byte("foobar"))) <-appGossipChan } @@ -94,7 +94,7 @@ func TestNewClient_AppRequest(t *testing.T) { }, } - client := NewClient(t, ctx, testHandler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + client := NewClient(t, ctx, testHandler) require.NoError(tt.appRequestF( ctx, client, diff --git a/x/sync/client_test.go b/x/sync/client_test.go index decc3e20405d..2633071439da 100644 --- a/x/sync/client_test.go +++ b/x/sync/client_test.go @@ -38,12 +38,12 @@ func newDefaultDBConfig() merkledb.Config { } } -func newFlakyRangeProofHandler( +func newModifiedRangeProofHandler( t *testing.T, db merkledb.MerkleDB, modifyResponse func(response *merkledb.RangeProof), ) p2p.Handler { - handler := NewGetRangeProofHandler(logging.NoLog{}, db) + handler := NewSyncGetRangeProofHandler(logging.NoLog{}, db) c := counter{m: 2} return &p2p.TestHandler{ @@ -74,12 +74,12 @@ func newFlakyRangeProofHandler( } } -func newFlakyChangeProofHandler( +func newModifiedChangeProofHandler( t *testing.T, db merkledb.MerkleDB, modifyResponse func(response *merkledb.ChangeProof), ) p2p.Handler { - handler := NewGetChangeProofHandler(logging.NoLog{}, db) + handler := NewSyncGetChangeProofHandler(logging.NoLog{}, db) c := counter{m: 2} return &p2p.TestHandler{ @@ -145,14 +145,3 @@ func (c *counter) Inc() int { c.i++ return result } - -type waitingHandler struct { - p2p.NoOpHandler - handler p2p.Handler - updatedRootChan chan struct{} -} - -func (w *waitingHandler) AppRequest(ctx context.Context, nodeID ids.NodeID, deadline time.Time, requestBytes []byte) ([]byte, *common.AppError) { - <-w.updatedRootChan - return w.handler.AppRequest(ctx, nodeID, deadline, requestBytes) -} diff --git a/x/sync/manager.go b/x/sync/manager.go index ddcdc1637088..dd176c223033 100644 --- a/x/sync/manager.go +++ b/x/sync/manager.go @@ -41,7 +41,7 @@ var ( ErrAlreadyStarted = errors.New("cannot start a Manager that has already been started") ErrAlreadyClosed = errors.New("Manager is closed") ErrNoRangeProofClientProvided = errors.New("range proof client is a required field of the sync config") - ErrNoChangeProofClientProvided = errors.New("change proof client is a required field of the sync config") + ErrNoChangeProofClientProvided = errors.New("change proofclient is a required field of the sync config") ErrNoDatabaseProvided = errors.New("sync database is a required field of the sync config") ErrNoLogProvided = errors.New("log is a required field of the sync config") ErrZeroWorkLimit = errors.New("simultaneous work limit must be greater than 0") @@ -305,12 +305,7 @@ func (m *Manager) doWork(ctx context.Context, work *workItem) { return } - select { - case <-ctx.Done(): - m.finishWorkItem() - return - case <-time.After(waitTime): - } + <-time.After(waitTime) if work.localRootID == ids.Empty { // the keys in this range have not been downloaded, so get all key/values @@ -373,8 +368,7 @@ func (m *Manager) requestChangeProof(ctx context.Context, work *workItem) { defer m.finishWorkItem() if err := m.handleChangeProofResponse(ctx, targetRootID, work, request, responseBytes, err); err != nil { - // TODO log responses - m.config.Log.Debug("dropping response", zap.Error(err), zap.Stringer("request", request)) + m.config.Log.Debug("dropping response", zap.Error(err)) m.retryWork(work) return } @@ -431,8 +425,7 @@ func (m *Manager) requestRangeProof(ctx context.Context, work *workItem) { defer m.finishWorkItem() if err := m.handleRangeProofResponse(ctx, targetRootID, work, request, responseBytes, appErr); err != nil { - // TODO log responses - m.config.Log.Debug("dropping response", zap.Error(err), zap.Stringer("request", request)) + m.config.Log.Debug("dropping response", zap.Error(err)) m.retryWork(work) return } @@ -468,11 +461,10 @@ func (m *Manager) retryWork(work *workItem) { m.workLock.Lock() m.unprocessedWork.Insert(work) m.workLock.Unlock() - m.unprocessedWorkCond.Signal() } // Returns an error if we should drop the response -func (m *Manager) shouldHandleResponse( +func (m *Manager) handleResponse( bytesLimit uint32, responseBytes []byte, err error, @@ -507,7 +499,7 @@ func (m *Manager) handleRangeProofResponse( responseBytes []byte, err error, ) error { - if err := m.shouldHandleResponse(request.BytesLimit, responseBytes, err); err != nil { + if err := m.handleResponse(request.BytesLimit, responseBytes, err); err != nil { return err } @@ -558,7 +550,7 @@ func (m *Manager) handleChangeProofResponse( responseBytes []byte, err error, ) error { - if err := m.shouldHandleResponse(request.BytesLimit, responseBytes, err); err != nil { + if err := m.handleResponse(request.BytesLimit, responseBytes, err); err != nil { return err } @@ -614,6 +606,7 @@ func (m *Manager) handleChangeProofResponse( m.completeWorkItem(ctx, work, largestHandledKey, targetRootID, changeProof.EndProof) case *pb.SyncGetChangeProofResponse_RangeProof: + var rangeProof merkledb.RangeProof if err := rangeProof.UnmarshalProto(changeProofResp.RangeProof); err != nil { return err diff --git a/x/sync/network_server.go b/x/sync/network_server.go index 2153f2fbcc97..ec70c2335b64 100644 --- a/x/sync/network_server.go +++ b/x/sync/network_server.go @@ -49,8 +49,8 @@ var ( errInvalidBounds = errors.New("start key is greater than end key") errInvalidRootHash = fmt.Errorf("root hash must have length %d", hashing.HashLen) - _ p2p.Handler = (*GetChangeProofHandler)(nil) - _ p2p.Handler = (*GetRangeProofHandler)(nil) + _ p2p.Handler = (*SyncGetChangeProofHandler)(nil) + _ p2p.Handler = (*SyncGetRangeProofHandler)(nil) ) func maybeBytesToMaybe(mb *pb.MaybeBytes) maybe.Maybe[[]byte] { @@ -60,30 +60,30 @@ func maybeBytesToMaybe(mb *pb.MaybeBytes) maybe.Maybe[[]byte] { return maybe.Nothing[[]byte]() } -func NewGetChangeProofHandler(log logging.Logger, db DB) *GetChangeProofHandler { - return &GetChangeProofHandler{ +func NewSyncGetChangeProofHandler(log logging.Logger, db DB) *SyncGetChangeProofHandler { + return &SyncGetChangeProofHandler{ log: log, db: db, } } -type GetChangeProofHandler struct { +type SyncGetChangeProofHandler struct { log logging.Logger db DB } -func (*GetChangeProofHandler) AppGossip(context.Context, ids.NodeID, []byte) {} +func (*SyncGetChangeProofHandler) AppGossip(context.Context, ids.NodeID, []byte) {} -func (g *GetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ time.Time, requestBytes []byte) ([]byte, *common.AppError) { - req := &pb.SyncGetChangeProofRequest{} - if err := proto.Unmarshal(requestBytes, req); err != nil { +func (s *SyncGetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ time.Time, requestBytes []byte) ([]byte, *common.AppError) { + request := &pb.SyncGetChangeProofRequest{} + if err := proto.Unmarshal(requestBytes, request); err != nil { return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, Message: fmt.Sprintf("failed to unmarshal request: %s", err), } } - if err := validateChangeProofRequest(req); err != nil { + if err := validateChangeProofRequest(request); err != nil { return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, Message: fmt.Sprintf("invalid request: %s", err), @@ -92,13 +92,13 @@ func (g *GetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ // override limits if they exceed caps var ( - keyLimit = min(req.KeyLimit, maxKeyValuesLimit) - bytesLimit = min(int(req.BytesLimit), maxByteSizeLimit) - start = maybeBytesToMaybe(req.StartKey) - end = maybeBytesToMaybe(req.EndKey) + keyLimit = min(request.KeyLimit, maxKeyValuesLimit) + bytesLimit = min(int(request.BytesLimit), maxByteSizeLimit) + start = maybeBytesToMaybe(request.StartKey) + end = maybeBytesToMaybe(request.EndKey) ) - startRoot, err := ids.ToID(req.StartRootHash) + startRoot, err := ids.ToID(request.StartRootHash) if err != nil { return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, @@ -106,7 +106,7 @@ func (g *GetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ } } - endRoot, err := ids.ToID(req.EndRootHash) + endRoot, err := ids.ToID(request.EndRootHash) if err != nil { return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, @@ -120,7 +120,6 @@ func (g *GetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ if !errors.Is(err, merkledb.ErrInsufficientHistory) { // We should only fail to get a change proof if we have insufficient history. // Other errors are unexpected. - // TODO define custom errors return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, Message: fmt.Sprintf("failed to get change proof: %s", err), @@ -141,11 +140,11 @@ func (g *GetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ ctx, g.db, &pb.SyncGetRangeProofRequest{ - RootHash: req.EndRootHash, - StartKey: req.StartKey, - EndKey: req.EndKey, - KeyLimit: req.KeyLimit, - BytesLimit: req.BytesLimit, + RootHash: request.EndRootHash, + StartKey: request.StartKey, + EndKey: request.EndKey, + KeyLimit: request.KeyLimit, + BytesLimit: request.BytesLimit, }, func(rangeProof *merkledb.RangeProof) ([]byte, error) { return proto.Marshal(&pb.SyncGetChangeProofResponse{ @@ -192,30 +191,34 @@ func (g *GetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ } } -func NewGetRangeProofHandler(log logging.Logger, db DB) *GetRangeProofHandler { - return &GetRangeProofHandler{ +func (*SyncGetChangeProofHandler) CrossChainAppRequest(context.Context, ids.ID, time.Time, []byte) ([]byte, error) { + return nil, nil +} + +func NewSyncGetRangeProofHandler(log logging.Logger, db DB) *SyncGetRangeProofHandler { + return &SyncGetRangeProofHandler{ log: log, db: db, } } -type GetRangeProofHandler struct { +type SyncGetRangeProofHandler struct { log logging.Logger db DB } -func (*GetRangeProofHandler) AppGossip(context.Context, ids.NodeID, []byte) {} +func (*SyncGetRangeProofHandler) AppGossip(context.Context, ids.NodeID, []byte) {} -func (g *GetRangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ time.Time, requestBytes []byte) ([]byte, *common.AppError) { - req := &pb.SyncGetRangeProofRequest{} - if err := proto.Unmarshal(requestBytes, req); err != nil { +func (s *SyncGetRangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ time.Time, requestBytes []byte) ([]byte, *common.AppError) { + request := &pb.SyncGetRangeProofRequest{} + if err := proto.Unmarshal(requestBytes, request); err != nil { return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, Message: fmt.Sprintf("failed to unmarshal request: %s", err), } } - if err := validateRangeProofRequest(req); err != nil { + if err := validateRangeProofRequest(request); err != nil { return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, Message: fmt.Sprintf("invalid range proof request: %s", err), @@ -223,13 +226,13 @@ func (g *GetRangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ t } // override limits if they exceed caps - req.KeyLimit = min(req.KeyLimit, maxKeyValuesLimit) - req.BytesLimit = min(req.BytesLimit, maxByteSizeLimit) + request.KeyLimit = min(request.KeyLimit, maxKeyValuesLimit) + request.BytesLimit = min(request.BytesLimit, maxByteSizeLimit) proofBytes, err := getRangeProof( ctx, - g.db, - req, + s.db, + request, func(rangeProof *merkledb.RangeProof) ([]byte, error) { return proto.Marshal(rangeProof.ToProto()) }, @@ -244,6 +247,10 @@ func (g *GetRangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ t return proofBytes, nil } +func (*SyncGetRangeProofHandler) CrossChainAppRequest(context.Context, ids.ID, time.Time, []byte) ([]byte, error) { + return nil, nil +} + // Get the range proof specified by [req]. // If the generated proof is too large, the key limit is reduced // and the proof is regenerated. This process is repeated until diff --git a/x/sync/network_server_test.go b/x/sync/network_server_test.go index c78554cea59f..84dbd1c12682 100644 --- a/x/sync/network_server_test.go +++ b/x/sync/network_server_test.go @@ -85,7 +85,7 @@ func Test_Server_GetRangeProof(t *testing.T) { expectedErr: p2p.ErrUnexpected, }, { - name: "response bounded by key limit", + name: "key limit too large", request: &pb.SyncGetRangeProofRequest{ RootHash: smallTrieRoot[:], KeyLimit: 2 * defaultRequestKeyLimit, @@ -94,7 +94,7 @@ func Test_Server_GetRangeProof(t *testing.T) { expectedResponseLen: defaultRequestKeyLimit, }, { - name: "response bounded by byte limit", + name: "bytes limit too large", request: &pb.SyncGetRangeProofRequest{ RootHash: smallTrieRoot[:], KeyLimit: defaultRequestKeyLimit, @@ -118,7 +118,7 @@ func Test_Server_GetRangeProof(t *testing.T) { t.Run(test.name, func(t *testing.T) { require := require.New(t) - handler := NewGetRangeProofHandler(logging.NoLog{}, smallTrieDB) + handler := NewSyncGetRangeProofHandler(logging.NoLog{}, smallTrieDB) requestBytes, err := proto.Marshal(test.request) require.NoError(err) responseBytes, err := handler.AppRequest(context.Background(), test.nodeID, time.Time{}, requestBytes) @@ -130,12 +130,17 @@ func Test_Server_GetRangeProof(t *testing.T) { require.Nil(responseBytes) return } + require.NotNil(responseBytes) - var proofProto pb.RangeProof - require.NoError(proto.Unmarshal(responseBytes, &proofProto)) + var proof *merkledb.RangeProof + if !test.proofNil { + var proofProto pb.RangeProof + require.NoError(proto.Unmarshal(responseBytes, &proofProto)) - var proof merkledb.RangeProof - require.NoError(proof.UnmarshalProto(&proofProto)) + var p merkledb.RangeProof + require.NoError(p.UnmarshalProto(&proofProto)) + proof = &p + } if test.expectedResponseLen > 0 { require.LessOrEqual(len(proof.KeyValues), test.expectedResponseLen) @@ -339,7 +344,7 @@ func Test_Server_GetChangeProof(t *testing.T) { t.Run(test.name, func(t *testing.T) { require := require.New(t) - handler := NewGetChangeProofHandler(logging.NoLog{}, serverDB) + handler := NewSyncGetChangeProofHandler(logging.NoLog{}, serverDB) requestBytes, err := proto.Marshal(test.request) require.NoError(err) diff --git a/x/sync/sync_test.go b/x/sync/sync_test.go index 41dd5829a7b8..db480f90f0c7 100644 --- a/x/sync/sync_test.go +++ b/x/sync/sync_test.go @@ -19,12 +19,13 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/network/p2p/p2ptest" + "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/maybe" "github.com/ava-labs/avalanchego/x/merkledb" ) -var _ p2p.Handler = (*waitingHandler)(nil) +var _ p2p.Handler = (*testHandler)(nil) func Test_Creation(t *testing.T) { require := require.New(t) @@ -39,8 +40,8 @@ func Test_Creation(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: &p2p.Client{}, + ChangeProofClient: &p2p.Client{}, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, BranchFactor: merkledb.BranchFactor16, @@ -72,8 +73,8 @@ func Test_Completion(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, emptyDB), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, emptyDB), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, emptyDB)), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, emptyDB)), TargetRoot: emptyRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -177,8 +178,8 @@ func Test_Sync_FindNextKey_InSync(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync)), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync)), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -253,8 +254,8 @@ func Test_Sync_FindNextKey_Deleted(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: &p2p.Client{}, + ChangeProofClient: &p2p.Client{}, TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -303,8 +304,8 @@ func Test_Sync_FindNextKey_BranchInLocal(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: &p2p.Client{}, + ChangeProofClient: &p2p.Client{}, TargetRoot: targetRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -340,8 +341,8 @@ func Test_Sync_FindNextKey_BranchInReceived(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: &p2p.Client{}, + ChangeProofClient: &p2p.Client{}, TargetRoot: targetRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -376,8 +377,8 @@ func Test_Sync_FindNextKey_ExtraValues(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync)), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync)), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -438,8 +439,8 @@ func TestFindNextKeyEmptyEndProof(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: &p2p.Client{}, + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, db)), TargetRoot: ids.Empty, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -507,8 +508,8 @@ func Test_Sync_FindNextKey_DifferentChild(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync)), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync)), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -540,6 +541,7 @@ func Test_Sync_FindNextKey_DifferentChild(t *testing.T) { // Test findNextKey by computing the expected result in a naive, inefficient // way and comparing it to the actual result + func TestFindNextKeyRandom(t *testing.T) { now := time.Now().UnixNano() t.Logf("seed: %d", now) @@ -730,8 +732,8 @@ func TestFindNextKeyRandom(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: localDB, - RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, remoteDB), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, remoteDB), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: &p2p.Client{}, + ChangeProofClient: &p2p.Client{}, TargetRoot: ids.GenerateTestID(), SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -773,27 +775,27 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { { name: "range proof bad response - too many leaves in response", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { response.KeyValues = append(response.KeyValues, merkledb.KeyValue{}) }) - return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + return p2ptest.NewClient(t, context.Background(), handler) }, }, { name: "range proof bad response - removed first key in response", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { response.KeyValues = response.KeyValues[min(1, len(response.KeyValues)):] }) - return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + return p2ptest.NewClient(t, context.Background(), handler) }, }, { name: "range proof bad response - removed first key in response and replaced proof", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { response.KeyValues = response.KeyValues[min(1, len(response.KeyValues)):] response.KeyValues = []merkledb.KeyValue{ { @@ -813,111 +815,111 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { } }) - return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + return p2ptest.NewClient(t, context.Background(), handler) }, }, { name: "range proof bad response - removed key from middle of response", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { i := rand.Intn(max(1, len(response.KeyValues)-1)) // #nosec G404 _ = slices.Delete(response.KeyValues, i, min(len(response.KeyValues), i+1)) }) - return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + return p2ptest.NewClient(t, context.Background(), handler) }, }, { name: "range proof bad response - start and end proof nodes removed", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { response.StartProof = nil response.EndProof = nil }) - return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + return p2ptest.NewClient(t, context.Background(), handler) }, }, { name: "range proof bad response - end proof removed", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { response.EndProof = nil }) - return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + return p2ptest.NewClient(t, context.Background(), handler) }, }, { name: "range proof bad response - empty proof", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { response.StartProof = nil response.EndProof = nil response.KeyValues = nil }) - return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + return p2ptest.NewClient(t, context.Background(), handler) }, }, { name: "range proof server flake", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { return p2ptest.NewClient(t, context.Background(), &flakyHandler{ - Handler: NewGetRangeProofHandler(logging.NoLog{}, db), + Handler: NewSyncGetRangeProofHandler(logging.NoLog{}, db), c: &counter{m: 2}, - }, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + }) }, }, { name: "change proof bad response - too many keys in response", changeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newFlakyChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { + handler := newModifiedChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { response.KeyChanges = append(response.KeyChanges, make([]merkledb.KeyChange, defaultRequestKeyLimit)...) }) - return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + return p2ptest.NewClient(t, context.Background(), handler) }, }, { name: "change proof bad response - removed first key in response", changeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newFlakyChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { + handler := newModifiedChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { response.KeyChanges = response.KeyChanges[min(1, len(response.KeyChanges)):] }) - return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + return p2ptest.NewClient(t, context.Background(), handler) }, }, { name: "change proof bad response - removed key from middle of response", changeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newFlakyChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { + handler := newModifiedChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { i := rand.Intn(max(1, len(response.KeyChanges)-1)) // #nosec G404 _ = slices.Delete(response.KeyChanges, i, min(len(response.KeyChanges), i+1)) }) - return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + return p2ptest.NewClient(t, context.Background(), handler) }, }, { - name: "change proof bad response - all proof keys removed from response", + name: "all proof keys removed from response", changeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newFlakyChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { + handler := newModifiedChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { response.StartProof = nil response.EndProof = nil }) - return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + return p2ptest.NewClient(t, context.Background(), handler) }, }, { - name: "change proof flaky server", + name: "flaky change proof client", changeProofClient: func(db merkledb.MerkleDB) *p2p.Client { return p2ptest.NewClient(t, context.Background(), &flakyHandler{ - Handler: NewGetChangeProofHandler(logging.NoLog{}, db), + Handler: NewSyncGetChangeProofHandler(logging.NoLog{}, db), c: &counter{m: 2}, - }, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + }) }, }, } @@ -945,14 +947,14 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { changeProofClient *p2p.Client ) - rangeProofHandler := NewGetRangeProofHandler(logging.NoLog{}, dbToSync) - rangeProofClient = p2ptest.NewClient(t, ctx, rangeProofHandler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + rangeProofHandler := NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync) + rangeProofClient = p2ptest.NewClient(t, ctx, rangeProofHandler) if tt.rangeProofClient != nil { rangeProofClient = tt.rangeProofClient(dbToSync) } - changeProofHandler := NewGetChangeProofHandler(logging.NoLog{}, dbToSync) - changeProofClient = p2ptest.NewClient(t, ctx, changeProofHandler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + changeProofHandler := NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync) + changeProofClient = p2ptest.NewClient(t, ctx, changeProofHandler) if tt.changeProofClient != nil { changeProofClient = tt.changeProofClient(dbToSync) } @@ -974,12 +976,8 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { require.NoError(syncer.Start(ctx)) // Simulate writes on the server - // - // TODO add more writes when api is not flaky. There is an inherent - // race condition in between writes where UpdateSyncTarget might - // error because it has already reached the sync target before it - // is called. - for i := 0; i < 50; i++ { + // TODO more than a single write when API is less flaky + for i := 0; i <= 1; i++ { addkey := make([]byte, r.Intn(50)) _, err = r.Read(addkey) require.NoError(err) @@ -1031,8 +1029,8 @@ func Test_Sync_Result_Correct_Root_With_Sync_Restart(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync)), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync)), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -1058,8 +1056,8 @@ func Test_Sync_Result_Correct_Root_With_Sync_Restart(t *testing.T) { newSyncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync)), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync)), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -1128,15 +1126,15 @@ func Test_Sync_Result_Correct_Root_Update_Root_During(t *testing.T) { updatedRootChan <- struct{}{} ctx := context.Background() - rangeProofClient := p2ptest.NewClient(t, ctx, &waitingHandler{ - handler: NewGetRangeProofHandler(logging.NoLog{}, dbToSync), + rangeProofClient := p2ptest.NewClient(t, ctx, &testHandler{ + handler: NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync), updatedRootChan: updatedRootChan, - }, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + }) - changeProofClient := p2ptest.NewClient(t, ctx, &waitingHandler{ - handler: NewGetChangeProofHandler(logging.NoLog{}, dbToSync), + changeProofClient := p2ptest.NewClient(t, ctx, &testHandler{ + handler: NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync), updatedRootChan: updatedRootChan, - }, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) + }) syncer, err := NewManager(ManagerConfig{ DB: db, @@ -1184,11 +1182,10 @@ func Test_Sync_UpdateSyncTarget(t *testing.T) { newDefaultDBConfig(), ) require.NoError(err) - ctx := context.Background() m, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: &p2p.Client{}, + ChangeProofClient: &p2p.Client{}, TargetRoot: ids.Empty, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -1287,3 +1284,14 @@ func generateTrieWithMinKeyLen(t *testing.T, r *rand.Rand, count int, minKeyLen } return db, batch.Write() } + +type testHandler struct { + p2p.NoOpHandler + handler p2p.Handler + updatedRootChan chan struct{} +} + +func (t *testHandler) AppRequest(ctx context.Context, nodeID ids.NodeID, deadline time.Time, requestBytes []byte) ([]byte, *common.AppError) { + <-t.updatedRootChan + return t.handler.AppRequest(ctx, nodeID, deadline, requestBytes) +} From 34fe3a673fd486d13e1c57f8cee74a7c75abe2a2 Mon Sep 17 00:00:00 2001 From: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:28:56 -0400 Subject: [PATCH 089/199] nit Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- network/p2p/p2ptest/client.go | 10 +++-- network/p2p/p2ptest/client_test.go | 4 +- x/sync/network_server.go | 48 ++++++++++-------------- x/sync/sync_test.go | 60 +++++++++++++++--------------- 4 files changed, 59 insertions(+), 63 deletions(-) diff --git a/network/p2p/p2ptest/client.go b/network/p2p/p2ptest/client.go index 747904b40ffb..8d41d6b99bce 100644 --- a/network/p2p/p2ptest/client.go +++ b/network/p2p/p2ptest/client.go @@ -21,15 +21,19 @@ import ( // NewClient generates a client-server pair and returns the client used to // communicate with a server with the specified handler -func NewClient(t *testing.T, rootCtx context.Context, handler p2p.Handler) *p2p.Client { +func NewClient( + t *testing.T, + rootCtx context.Context, + handler p2p.Handler, + clientNodeID ids.NodeID, + serverNodeID ids.NodeID, +) *p2p.Client { clientSender := &enginetest.Sender{} serverSender := &enginetest.Sender{} - clientNodeID := ids.GenerateTestNodeID() clientNetwork, err := p2p.NewNetwork(logging.NoLog{}, clientSender, prometheus.NewRegistry(), "") require.NoError(t, err) - serverNodeID := ids.GenerateTestNodeID() serverNetwork, err := p2p.NewNetwork(logging.NoLog{}, serverSender, prometheus.NewRegistry(), "") require.NoError(t, err) diff --git a/network/p2p/p2ptest/client_test.go b/network/p2p/p2ptest/client_test.go index cef624aaccbc..45ae970ecf0f 100644 --- a/network/p2p/p2ptest/client_test.go +++ b/network/p2p/p2ptest/client_test.go @@ -27,7 +27,7 @@ func TestNewClient_AppGossip(t *testing.T) { }, } - client := NewClient(t, ctx, testHandler) + client := NewClient(t, ctx, testHandler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) require.NoError(client.AppGossip(ctx, common.SendConfig{}, []byte("foobar"))) <-appGossipChan } @@ -94,7 +94,7 @@ func TestNewClient_AppRequest(t *testing.T) { }, } - client := NewClient(t, ctx, testHandler) + client := NewClient(t, ctx, testHandler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) require.NoError(tt.appRequestF( ctx, client, diff --git a/x/sync/network_server.go b/x/sync/network_server.go index ec70c2335b64..10d86ed140eb 100644 --- a/x/sync/network_server.go +++ b/x/sync/network_server.go @@ -75,15 +75,15 @@ type SyncGetChangeProofHandler struct { func (*SyncGetChangeProofHandler) AppGossip(context.Context, ids.NodeID, []byte) {} func (s *SyncGetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ time.Time, requestBytes []byte) ([]byte, *common.AppError) { - request := &pb.SyncGetChangeProofRequest{} - if err := proto.Unmarshal(requestBytes, request); err != nil { + req := &pb.SyncGetChangeProofRequest{} + if err := proto.Unmarshal(requestBytes, req); err != nil { return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, Message: fmt.Sprintf("failed to unmarshal request: %s", err), } } - if err := validateChangeProofRequest(request); err != nil { + if err := validateChangeProofRequest(req); err != nil { return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, Message: fmt.Sprintf("invalid request: %s", err), @@ -92,13 +92,13 @@ func (s *SyncGetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID // override limits if they exceed caps var ( - keyLimit = min(request.KeyLimit, maxKeyValuesLimit) - bytesLimit = min(int(request.BytesLimit), maxByteSizeLimit) - start = maybeBytesToMaybe(request.StartKey) - end = maybeBytesToMaybe(request.EndKey) + keyLimit = min(req.KeyLimit, maxKeyValuesLimit) + bytesLimit = min(int(req.BytesLimit), maxByteSizeLimit) + start = maybeBytesToMaybe(req.StartKey) + end = maybeBytesToMaybe(req.EndKey) ) - startRoot, err := ids.ToID(request.StartRootHash) + startRoot, err := ids.ToID(req.StartRootHash) if err != nil { return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, @@ -106,7 +106,7 @@ func (s *SyncGetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID } } - endRoot, err := ids.ToID(request.EndRootHash) + endRoot, err := ids.ToID(req.EndRootHash) if err != nil { return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, @@ -140,11 +140,11 @@ func (s *SyncGetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID ctx, g.db, &pb.SyncGetRangeProofRequest{ - RootHash: request.EndRootHash, - StartKey: request.StartKey, - EndKey: request.EndKey, - KeyLimit: request.KeyLimit, - BytesLimit: request.BytesLimit, + RootHash: req.EndRootHash, + StartKey: req.StartKey, + EndKey: req.EndKey, + KeyLimit: req.KeyLimit, + BytesLimit: req.BytesLimit, }, func(rangeProof *merkledb.RangeProof) ([]byte, error) { return proto.Marshal(&pb.SyncGetChangeProofResponse{ @@ -191,10 +191,6 @@ func (s *SyncGetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID } } -func (*SyncGetChangeProofHandler) CrossChainAppRequest(context.Context, ids.ID, time.Time, []byte) ([]byte, error) { - return nil, nil -} - func NewSyncGetRangeProofHandler(log logging.Logger, db DB) *SyncGetRangeProofHandler { return &SyncGetRangeProofHandler{ log: log, @@ -210,15 +206,15 @@ type SyncGetRangeProofHandler struct { func (*SyncGetRangeProofHandler) AppGossip(context.Context, ids.NodeID, []byte) {} func (s *SyncGetRangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ time.Time, requestBytes []byte) ([]byte, *common.AppError) { - request := &pb.SyncGetRangeProofRequest{} - if err := proto.Unmarshal(requestBytes, request); err != nil { + req := &pb.SyncGetRangeProofRequest{} + if err := proto.Unmarshal(requestBytes, req); err != nil { return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, Message: fmt.Sprintf("failed to unmarshal request: %s", err), } } - if err := validateRangeProofRequest(request); err != nil { + if err := validateRangeProofRequest(req); err != nil { return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, Message: fmt.Sprintf("invalid range proof request: %s", err), @@ -226,13 +222,13 @@ func (s *SyncGetRangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, } // override limits if they exceed caps - request.KeyLimit = min(request.KeyLimit, maxKeyValuesLimit) - request.BytesLimit = min(request.BytesLimit, maxByteSizeLimit) + req.KeyLimit = min(req.KeyLimit, maxKeyValuesLimit) + req.BytesLimit = min(req.BytesLimit, maxByteSizeLimit) proofBytes, err := getRangeProof( ctx, s.db, - request, + req, func(rangeProof *merkledb.RangeProof) ([]byte, error) { return proto.Marshal(rangeProof.ToProto()) }, @@ -247,10 +243,6 @@ func (s *SyncGetRangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, return proofBytes, nil } -func (*SyncGetRangeProofHandler) CrossChainAppRequest(context.Context, ids.ID, time.Time, []byte) ([]byte, error) { - return nil, nil -} - // Get the range proof specified by [req]. // If the generated proof is too large, the key limit is reduced // and the proof is regenerated. This process is repeated until diff --git a/x/sync/sync_test.go b/x/sync/sync_test.go index db480f90f0c7..7373c08bf5c5 100644 --- a/x/sync/sync_test.go +++ b/x/sync/sync_test.go @@ -73,8 +73,8 @@ func Test_Completion(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, emptyDB)), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, emptyDB)), + RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, emptyDB), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, emptyDB), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: emptyRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -178,8 +178,8 @@ func Test_Sync_FindNextKey_InSync(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync)), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync)), + RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -377,8 +377,8 @@ func Test_Sync_FindNextKey_ExtraValues(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync)), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync)), + RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -440,7 +440,7 @@ func TestFindNextKeyEmptyEndProof(t *testing.T) { syncer, err := NewManager(ManagerConfig{ DB: db, RangeProofClient: &p2p.Client{}, - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, db)), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: ids.Empty, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -508,8 +508,8 @@ func Test_Sync_FindNextKey_DifferentChild(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync)), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync)), + RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -779,7 +779,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { response.KeyValues = append(response.KeyValues, merkledb.KeyValue{}) }) - return p2ptest.NewClient(t, context.Background(), handler) + return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, }, { @@ -789,7 +789,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { response.KeyValues = response.KeyValues[min(1, len(response.KeyValues)):] }) - return p2ptest.NewClient(t, context.Background(), handler) + return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, }, { @@ -815,7 +815,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { } }) - return p2ptest.NewClient(t, context.Background(), handler) + return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, }, { @@ -826,7 +826,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { _ = slices.Delete(response.KeyValues, i, min(len(response.KeyValues), i+1)) }) - return p2ptest.NewClient(t, context.Background(), handler) + return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, }, { @@ -837,7 +837,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { response.EndProof = nil }) - return p2ptest.NewClient(t, context.Background(), handler) + return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, }, { @@ -847,7 +847,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { response.EndProof = nil }) - return p2ptest.NewClient(t, context.Background(), handler) + return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, }, { @@ -859,7 +859,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { response.KeyValues = nil }) - return p2ptest.NewClient(t, context.Background(), handler) + return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, }, { @@ -868,7 +868,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { return p2ptest.NewClient(t, context.Background(), &flakyHandler{ Handler: NewSyncGetRangeProofHandler(logging.NoLog{}, db), c: &counter{m: 2}, - }) + }, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, }, { @@ -878,7 +878,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { response.KeyChanges = append(response.KeyChanges, make([]merkledb.KeyChange, defaultRequestKeyLimit)...) }) - return p2ptest.NewClient(t, context.Background(), handler) + return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, }, { @@ -888,7 +888,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { response.KeyChanges = response.KeyChanges[min(1, len(response.KeyChanges)):] }) - return p2ptest.NewClient(t, context.Background(), handler) + return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, }, { @@ -899,7 +899,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { _ = slices.Delete(response.KeyChanges, i, min(len(response.KeyChanges), i+1)) }) - return p2ptest.NewClient(t, context.Background(), handler) + return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, }, { @@ -910,7 +910,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { response.EndProof = nil }) - return p2ptest.NewClient(t, context.Background(), handler) + return p2ptest.NewClient(t, context.Background(), handler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, }, { @@ -919,7 +919,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { return p2ptest.NewClient(t, context.Background(), &flakyHandler{ Handler: NewSyncGetChangeProofHandler(logging.NoLog{}, db), c: &counter{m: 2}, - }) + }, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, }, } @@ -948,13 +948,13 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { ) rangeProofHandler := NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync) - rangeProofClient = p2ptest.NewClient(t, ctx, rangeProofHandler) + rangeProofClient = p2ptest.NewClient(t, ctx, rangeProofHandler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) if tt.rangeProofClient != nil { rangeProofClient = tt.rangeProofClient(dbToSync) } changeProofHandler := NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync) - changeProofClient = p2ptest.NewClient(t, ctx, changeProofHandler) + changeProofClient = p2ptest.NewClient(t, ctx, changeProofHandler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) if tt.changeProofClient != nil { changeProofClient = tt.changeProofClient(dbToSync) } @@ -1029,8 +1029,8 @@ func Test_Sync_Result_Correct_Root_With_Sync_Restart(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync)), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync)), + RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -1056,8 +1056,8 @@ func Test_Sync_Result_Correct_Root_With_Sync_Restart(t *testing.T) { newSyncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync)), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync)), + RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -1129,12 +1129,12 @@ func Test_Sync_Result_Correct_Root_Update_Root_During(t *testing.T) { rangeProofClient := p2ptest.NewClient(t, ctx, &testHandler{ handler: NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync), updatedRootChan: updatedRootChan, - }) + }, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) changeProofClient := p2ptest.NewClient(t, ctx, &testHandler{ handler: NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync), updatedRootChan: updatedRootChan, - }) + }, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) syncer, err := NewManager(ManagerConfig{ DB: db, From b9d550771feb4b4bcae3342a34774b1cdf567c39 Mon Sep 17 00:00:00 2001 From: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:28:56 -0400 Subject: [PATCH 090/199] add acp-118 implementation Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- network/acp118/aggregator.go | 229 ++++++++++++++++++++++++++++++ network/acp118/aggregator_test.go | 205 ++++++++++++++++++++++++++ network/acp118/handler.go | 107 ++++++++++++++ network/acp118/handler_test.go | 118 +++++++++++++++ network/p2p/error.go | 6 + 5 files changed, 665 insertions(+) create mode 100644 network/acp118/aggregator.go create mode 100644 network/acp118/aggregator_test.go create mode 100644 network/acp118/handler.go create mode 100644 network/acp118/handler_test.go diff --git a/network/acp118/aggregator.go b/network/acp118/aggregator.go new file mode 100644 index 000000000000..9cbc10a9442c --- /dev/null +++ b/network/acp118/aggregator.go @@ -0,0 +1,229 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package acp118 + +import ( + "context" + "errors" + "fmt" + "sync" + + "go.uber.org/zap" + "golang.org/x/sync/semaphore" + "google.golang.org/protobuf/proto" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" +) + +var ( + ErrDuplicateValidator = errors.New("duplicate validator") + ErrInsufficientSignatures = errors.New("failed to aggregate sufficient stake weight of signatures") +) + +type result struct { + message *warp.Message + err error +} + +type Validator struct { + NodeID ids.NodeID + PublicKey *bls.PublicKey + Weight uint64 +} + +type indexedValidator struct { + Validator + I int +} + +// NewSignatureAggregator returns an instance of SignatureAggregator +func NewSignatureAggregator( + log logging.Logger, + client *p2p.Client, + maxPending int, +) *SignatureAggregator { + return &SignatureAggregator{ + log: log, + client: client, + maxPending: int64(maxPending), + } +} + +// SignatureAggregator aggregates validator signatures for warp messages +type SignatureAggregator struct { + log logging.Logger + client *p2p.Client + maxPending int64 +} + +// AggregateSignatures blocks until stakeWeightThreshold of validators signs the +// provided message. Validators are issued requests in the caller-specified +// order. +func (s *SignatureAggregator) AggregateSignatures( + parentCtx context.Context, + message *warp.UnsignedMessage, + justification []byte, + validators []Validator, + stakeWeightThreshold uint64, +) (*warp.Message, error) { + ctx, cancel := context.WithCancel(parentCtx) + defer cancel() + + request := &sdk.SignatureRequest{ + Message: message.Bytes(), + Justification: justification, + } + + requestBytes, err := proto.Marshal(request) + if err != nil { + return nil, fmt.Errorf("failed to marshal signature request: %w", err) + } + + done := make(chan result) + pendingRequests := semaphore.NewWeighted(s.maxPending) + lock := &sync.Mutex{} + aggregatedStakeWeight := uint64(0) + attemptedStakeWeight := uint64(0) + totalStakeWeight := uint64(0) + signatures := make([]*bls.Signature, 0) + signerBitSet := set.NewBits() + + nodeIDsToValidator := make(map[ids.NodeID]indexedValidator) + for i, v := range validators { + totalStakeWeight += v.Weight + + // Sanity check the validator set provided by the caller + if _, ok := nodeIDsToValidator[v.NodeID]; ok { + return nil, fmt.Errorf("%w: %s", ErrDuplicateValidator, v.NodeID) + } + + nodeIDsToValidator[v.NodeID] = indexedValidator{ + I: i, + Validator: v, + } + } + + onResponse := func( + _ context.Context, + nodeID ids.NodeID, + responseBytes []byte, + err error, + ) { + // We are guaranteed a response from a node in the validator set + validator := nodeIDsToValidator[nodeID] + + defer func() { + lock.Lock() + attemptedStakeWeight += validator.Weight + remainingStakeWeight := totalStakeWeight - attemptedStakeWeight + failed := remainingStakeWeight < stakeWeightThreshold + lock.Unlock() + + if failed { + done <- result{err: ErrInsufficientSignatures} + } + + pendingRequests.Release(1) + }() + + if err != nil { + s.log.Debug( + "dropping response", + zap.Stringer("nodeID", nodeID), + zap.Error(err), + ) + return + } + + response := &sdk.SignatureResponse{} + if err := proto.Unmarshal(responseBytes, response); err != nil { + s.log.Debug( + "dropping response", + zap.Stringer("nodeID", nodeID), + zap.Error(err), + ) + return + } + + signature, err := bls.SignatureFromBytes(response.Signature) + if err != nil { + s.log.Debug( + "dropping response", + zap.Stringer("nodeID", nodeID), + zap.String("reason", "invalid signature"), + zap.Error(err), + ) + return + } + + if !bls.Verify(validator.PublicKey, signature, message.Bytes()) { + s.log.Debug( + "dropping response", + zap.Stringer("nodeID", nodeID), + zap.String("reason", "public key failed verification"), + ) + return + } + + lock.Lock() + signerBitSet.Add(validator.I) + signatures = append(signatures, signature) + aggregatedStakeWeight += validator.Weight + + if aggregatedStakeWeight >= stakeWeightThreshold { + aggregateSignature, err := bls.AggregateSignatures(signatures) + if err != nil { + done <- result{err: err} + lock.Unlock() + return + } + + bitSetSignature := &warp.BitSetSignature{ + Signers: signerBitSet.Bytes(), + Signature: [bls.SignatureLen]byte{}, + } + + copy(bitSetSignature.Signature[:], bls.SignatureToBytes(aggregateSignature)) + signedMessage, err := warp.NewMessage(message, bitSetSignature) + done <- result{message: signedMessage, err: err} + lock.Unlock() + return + } + + lock.Unlock() + } + + for _, validator := range validators { + if err := pendingRequests.Acquire(ctx, 1); err != nil { + return nil, err + } + + // Avoid loop shadowing in goroutine + validatorCopy := validator + go func() { + if err := s.client.AppRequest( + ctx, + set.Of(validatorCopy.NodeID), + requestBytes, + onResponse, + ); err != nil { + done <- result{err: err} + return + } + }() + } + + select { + case <-ctx.Done(): + return nil, ctx.Err() + case r := <-done: + return r.message, r.err + } +} diff --git a/network/acp118/aggregator_test.go b/network/acp118/aggregator_test.go new file mode 100644 index 000000000000..50622fc4ac99 --- /dev/null +++ b/network/acp118/aggregator_test.go @@ -0,0 +1,205 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package acp118 + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/p2p/p2ptest" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/snow/validators/validatorstest" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" +) + +func TestVerifier_Verify(t *testing.T) { + nodeID0 := ids.GenerateTestNodeID() + sk0, err := bls.NewSecretKey() + require.NoError(t, err) + pk0 := bls.PublicFromSecretKey(sk0) + + nodeID1 := ids.GenerateTestNodeID() + sk1, err := bls.NewSecretKey() + require.NoError(t, err) + pk1 := bls.PublicFromSecretKey(sk1) + + networkID := uint32(123) + subnetID := ids.GenerateTestID() + chainID := ids.GenerateTestID() + signer := warp.NewSigner(sk0, networkID, chainID) + + tests := []struct { + name string + + handler p2p.Handler + + ctx context.Context + validators []Validator + threshold uint64 + + pChainState validators.State + pChainHeight uint64 + quorumNum uint64 + quorumDen uint64 + + wantAggregateSignaturesErr error + wantVerifyErr error + }{ + { + name: "passes attestation and verification", + handler: NewHandler(&testAttestor{}, signer, networkID, chainID), + ctx: context.Background(), + validators: []Validator{ + { + NodeID: nodeID0, + PublicKey: pk0, + Weight: 1, + }, + }, + threshold: 1, + pChainState: &validatorstest.State{ + T: t, + GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { + return subnetID, nil + }, + GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + return map[ids.NodeID]*validators.GetValidatorOutput{ + nodeID0: { + NodeID: nodeID0, + PublicKey: pk0, + Weight: 1, + }, + }, nil + }, + }, + quorumNum: 1, + quorumDen: 1, + }, + { + name: "passes attestation and fails verification - insufficient stake", + handler: NewHandler(&testAttestor{}, signer, networkID, chainID), + ctx: context.Background(), + validators: []Validator{ + { + NodeID: nodeID0, + PublicKey: pk0, + Weight: 1, + }, + }, + threshold: 1, + pChainState: &validatorstest.State{ + T: t, + GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { + return subnetID, nil + }, + GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + return map[ids.NodeID]*validators.GetValidatorOutput{ + nodeID0: { + NodeID: nodeID0, + PublicKey: pk0, + Weight: 1, + }, + nodeID1: { + NodeID: nodeID1, + PublicKey: pk1, + Weight: 1, + }, + }, nil + }, + }, + quorumNum: 2, + quorumDen: 2, + wantVerifyErr: warp.ErrInsufficientWeight, + }, + { + name: "fails attestation", + handler: NewHandler( + &testAttestor{Err: errors.New("foobar")}, + signer, + networkID, + chainID, + ), + ctx: context.Background(), + validators: []Validator{ + { + NodeID: nodeID0, + PublicKey: pk0, + Weight: 1, + }, + }, + threshold: 1, + wantAggregateSignaturesErr: ErrInsufficientSignatures, + }, + { + name: "invalid validator set", + ctx: context.Background(), + validators: []Validator{ + { + NodeID: nodeID0, + PublicKey: pk0, + Weight: 1, + }, + { + NodeID: nodeID0, + PublicKey: pk0, + Weight: 1, + }, + }, + wantAggregateSignaturesErr: ErrDuplicateValidator, + }, + { + name: "context canceled", + ctx: func() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + return ctx + }(), + wantAggregateSignaturesErr: context.Canceled, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + ctx := context.Background() + message, err := warp.NewUnsignedMessage(networkID, chainID, []byte("payload")) + require.NoError(err) + client := p2ptest.NewClient(t, ctx, tt.handler, ids.GenerateTestNodeID(), nodeID0) + verifier := NewSignatureAggregator(logging.NoLog{}, client, 1) + + signedMessage, err := verifier.AggregateSignatures( + tt.ctx, + message, + []byte("justification"), + tt.validators, + tt.threshold, + ) + require.ErrorIs(err, tt.wantAggregateSignaturesErr) + + if signedMessage == nil { + return + } + + err = signedMessage.Signature.Verify( + ctx, + &signedMessage.UnsignedMessage, + networkID, + tt.pChainState, + 0, + tt.quorumNum, + tt.quorumDen, + ) + require.ErrorIs(err, tt.wantVerifyErr) + }) + } +} diff --git a/network/acp118/handler.go b/network/acp118/handler.go new file mode 100644 index 000000000000..e4aae28e3ce6 --- /dev/null +++ b/network/acp118/handler.go @@ -0,0 +1,107 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package acp118 + +import ( + "context" + "fmt" + "time" + + "google.golang.org/protobuf/proto" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" +) + +var _ p2p.Handler = (*Handler)(nil) + +// Attestor defines whether to a warp message payload should be attested to +type Attestor interface { + Attest(message *warp.UnsignedMessage, justification []byte) (bool, error) +} + +// NewHandler returns an instance of Handler +func NewHandler( + attestor Attestor, + signer warp.Signer, + networkID uint32, + chainID ids.ID, +) *Handler { + return &Handler{ + attestor: attestor, + signer: signer, + networkID: networkID, + chainID: chainID, + } +} + +// Handler signs warp messages +type Handler struct { + p2p.NoOpHandler + + attestor Attestor + signer warp.Signer + networkID uint32 + chainID ids.ID +} + +func (h *Handler) AppRequest( + _ context.Context, + _ ids.NodeID, + _ time.Time, + requestBytes []byte, +) ([]byte, *common.AppError) { + request := &sdk.SignatureRequest{} + if err := proto.Unmarshal(requestBytes, request); err != nil { + return nil, &common.AppError{ + Code: p2p.ErrUnexpected.Code, + Message: fmt.Sprintf("failed to unmarshal request: %s", err), + } + } + + msg, err := warp.ParseUnsignedMessage(request.Message) + if err != nil { + return nil, &common.AppError{ + Code: p2p.ErrUnexpected.Code, + Message: fmt.Sprintf("failed to initialize warp unsigned message: %s", err), + } + } + + ok, err := h.attestor.Attest(msg, request.Justification) + if err != nil { + return nil, &common.AppError{ + Code: p2p.ErrUnexpected.Code, + Message: fmt.Sprintf("failed to attest request: %s", err), + } + } + + if !ok { + return nil, p2p.ErrAttestFailed + } + + signature, err := h.signer.Sign(msg) + if err != nil { + return nil, &common.AppError{ + Code: p2p.ErrUnexpected.Code, + Message: fmt.Sprintf("failed to sign message: %s", err), + } + } + + response := &sdk.SignatureResponse{ + Signature: signature, + } + + responseBytes, err := proto.Marshal(response) + if err != nil { + return nil, &common.AppError{ + Code: p2p.ErrUnexpected.Code, + Message: fmt.Sprintf("failed to marshal response: %s", err), + } + } + + return responseBytes, nil +} diff --git a/network/acp118/handler_test.go b/network/acp118/handler_test.go new file mode 100644 index 000000000000..77af9e8dd0fb --- /dev/null +++ b/network/acp118/handler_test.go @@ -0,0 +1,118 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package acp118 + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/p2p/p2ptest" + "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" +) + +var _ Attestor = (*testAttestor)(nil) + +func TestHandler(t *testing.T) { + tests := []struct { + name string + attestor Attestor + expectedErr error + expectedVerify bool + }{ + { + name: "signature fails attestation", + attestor: &testAttestor{Err: errors.New("foo")}, + expectedErr: p2p.ErrUnexpected, + }, + { + name: "signature not attested", + attestor: &testAttestor{CantAttest: true}, + expectedErr: p2p.ErrAttestFailed, + }, + { + name: "signature attested", + attestor: &testAttestor{}, + expectedVerify: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + ctx := context.Background() + sk, err := bls.NewSecretKey() + require.NoError(err) + pk := bls.PublicFromSecretKey(sk) + networkID := uint32(123) + chainID := ids.GenerateTestID() + signer := warp.NewSigner(sk, networkID, chainID) + h := NewHandler(tt.attestor, signer, networkID, chainID) + clientNodeID := ids.GenerateTestNodeID() + serverNodeID := ids.GenerateTestNodeID() + c := p2ptest.NewClient( + t, + ctx, + h, + clientNodeID, + serverNodeID, + ) + + unsignedMessage, err := warp.NewUnsignedMessage( + networkID, + chainID, + []byte("payload"), + ) + require.NoError(err) + + request := &sdk.SignatureRequest{ + Message: unsignedMessage.Bytes(), + Justification: []byte("justification"), + } + + requestBytes, err := proto.Marshal(request) + require.NoError(err) + + done := make(chan struct{}) + onResponse := func(_ context.Context, _ ids.NodeID, responseBytes []byte, appErr error) { + defer close(done) + + if appErr != nil { + require.ErrorIs(tt.expectedErr, appErr) + return + } + + response := &sdk.SignatureResponse{} + require.NoError(proto.Unmarshal(responseBytes, response)) + + signature, err := bls.SignatureFromBytes(response.Signature) + require.NoError(err) + + require.Equal(tt.expectedVerify, bls.Verify(pk, signature, request.Message)) + } + + require.NoError(c.AppRequest(ctx, set.Of(serverNodeID), requestBytes, onResponse)) + <-done + }) + } +} + +// The zero value of testAttestor attests +type testAttestor struct { + CantAttest bool + Err error +} + +func (t testAttestor) Attest(*warp.UnsignedMessage, []byte) (bool, error) { + return !t.CantAttest, t.Err +} diff --git a/network/p2p/error.go b/network/p2p/error.go index 07207319a041..67b0317153e6 100644 --- a/network/p2p/error.go +++ b/network/p2p/error.go @@ -30,4 +30,10 @@ var ( Code: -4, Message: "throttled", } + // ErrAttestFailed should be used to indicate that a request failed + // to be signed due to the peer being unable to attest the message + ErrAttestFailed = &common.AppError{ + Code: -5, + Message: "failed attestation", + } ) From 99f0cde16774eb42cc9e6b00a9ac432e7773e336 Mon Sep 17 00:00:00 2001 From: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:48:26 -0400 Subject: [PATCH 091/199] undo diff Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- network/acp118/aggregator.go | 229 ------------------------------ network/acp118/aggregator_test.go | 205 -------------------------- x/sync/client_test.go | 19 ++- x/sync/manager.go | 23 +-- x/sync/network_server.go | 27 ++-- x/sync/network_server_test.go | 21 ++- x/sync/sync_test.go | 118 +++++++-------- 7 files changed, 107 insertions(+), 535 deletions(-) delete mode 100644 network/acp118/aggregator.go delete mode 100644 network/acp118/aggregator_test.go diff --git a/network/acp118/aggregator.go b/network/acp118/aggregator.go deleted file mode 100644 index 9cbc10a9442c..000000000000 --- a/network/acp118/aggregator.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package acp118 - -import ( - "context" - "errors" - "fmt" - "sync" - - "go.uber.org/zap" - "golang.org/x/sync/semaphore" - "google.golang.org/protobuf/proto" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/network/p2p" - "github.com/ava-labs/avalanchego/proto/pb/sdk" - "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/utils/set" - "github.com/ava-labs/avalanchego/vms/platformvm/warp" -) - -var ( - ErrDuplicateValidator = errors.New("duplicate validator") - ErrInsufficientSignatures = errors.New("failed to aggregate sufficient stake weight of signatures") -) - -type result struct { - message *warp.Message - err error -} - -type Validator struct { - NodeID ids.NodeID - PublicKey *bls.PublicKey - Weight uint64 -} - -type indexedValidator struct { - Validator - I int -} - -// NewSignatureAggregator returns an instance of SignatureAggregator -func NewSignatureAggregator( - log logging.Logger, - client *p2p.Client, - maxPending int, -) *SignatureAggregator { - return &SignatureAggregator{ - log: log, - client: client, - maxPending: int64(maxPending), - } -} - -// SignatureAggregator aggregates validator signatures for warp messages -type SignatureAggregator struct { - log logging.Logger - client *p2p.Client - maxPending int64 -} - -// AggregateSignatures blocks until stakeWeightThreshold of validators signs the -// provided message. Validators are issued requests in the caller-specified -// order. -func (s *SignatureAggregator) AggregateSignatures( - parentCtx context.Context, - message *warp.UnsignedMessage, - justification []byte, - validators []Validator, - stakeWeightThreshold uint64, -) (*warp.Message, error) { - ctx, cancel := context.WithCancel(parentCtx) - defer cancel() - - request := &sdk.SignatureRequest{ - Message: message.Bytes(), - Justification: justification, - } - - requestBytes, err := proto.Marshal(request) - if err != nil { - return nil, fmt.Errorf("failed to marshal signature request: %w", err) - } - - done := make(chan result) - pendingRequests := semaphore.NewWeighted(s.maxPending) - lock := &sync.Mutex{} - aggregatedStakeWeight := uint64(0) - attemptedStakeWeight := uint64(0) - totalStakeWeight := uint64(0) - signatures := make([]*bls.Signature, 0) - signerBitSet := set.NewBits() - - nodeIDsToValidator := make(map[ids.NodeID]indexedValidator) - for i, v := range validators { - totalStakeWeight += v.Weight - - // Sanity check the validator set provided by the caller - if _, ok := nodeIDsToValidator[v.NodeID]; ok { - return nil, fmt.Errorf("%w: %s", ErrDuplicateValidator, v.NodeID) - } - - nodeIDsToValidator[v.NodeID] = indexedValidator{ - I: i, - Validator: v, - } - } - - onResponse := func( - _ context.Context, - nodeID ids.NodeID, - responseBytes []byte, - err error, - ) { - // We are guaranteed a response from a node in the validator set - validator := nodeIDsToValidator[nodeID] - - defer func() { - lock.Lock() - attemptedStakeWeight += validator.Weight - remainingStakeWeight := totalStakeWeight - attemptedStakeWeight - failed := remainingStakeWeight < stakeWeightThreshold - lock.Unlock() - - if failed { - done <- result{err: ErrInsufficientSignatures} - } - - pendingRequests.Release(1) - }() - - if err != nil { - s.log.Debug( - "dropping response", - zap.Stringer("nodeID", nodeID), - zap.Error(err), - ) - return - } - - response := &sdk.SignatureResponse{} - if err := proto.Unmarshal(responseBytes, response); err != nil { - s.log.Debug( - "dropping response", - zap.Stringer("nodeID", nodeID), - zap.Error(err), - ) - return - } - - signature, err := bls.SignatureFromBytes(response.Signature) - if err != nil { - s.log.Debug( - "dropping response", - zap.Stringer("nodeID", nodeID), - zap.String("reason", "invalid signature"), - zap.Error(err), - ) - return - } - - if !bls.Verify(validator.PublicKey, signature, message.Bytes()) { - s.log.Debug( - "dropping response", - zap.Stringer("nodeID", nodeID), - zap.String("reason", "public key failed verification"), - ) - return - } - - lock.Lock() - signerBitSet.Add(validator.I) - signatures = append(signatures, signature) - aggregatedStakeWeight += validator.Weight - - if aggregatedStakeWeight >= stakeWeightThreshold { - aggregateSignature, err := bls.AggregateSignatures(signatures) - if err != nil { - done <- result{err: err} - lock.Unlock() - return - } - - bitSetSignature := &warp.BitSetSignature{ - Signers: signerBitSet.Bytes(), - Signature: [bls.SignatureLen]byte{}, - } - - copy(bitSetSignature.Signature[:], bls.SignatureToBytes(aggregateSignature)) - signedMessage, err := warp.NewMessage(message, bitSetSignature) - done <- result{message: signedMessage, err: err} - lock.Unlock() - return - } - - lock.Unlock() - } - - for _, validator := range validators { - if err := pendingRequests.Acquire(ctx, 1); err != nil { - return nil, err - } - - // Avoid loop shadowing in goroutine - validatorCopy := validator - go func() { - if err := s.client.AppRequest( - ctx, - set.Of(validatorCopy.NodeID), - requestBytes, - onResponse, - ); err != nil { - done <- result{err: err} - return - } - }() - } - - select { - case <-ctx.Done(): - return nil, ctx.Err() - case r := <-done: - return r.message, r.err - } -} diff --git a/network/acp118/aggregator_test.go b/network/acp118/aggregator_test.go deleted file mode 100644 index 50622fc4ac99..000000000000 --- a/network/acp118/aggregator_test.go +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package acp118 - -import ( - "context" - "errors" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/network/p2p" - "github.com/ava-labs/avalanchego/network/p2p/p2ptest" - "github.com/ava-labs/avalanchego/snow/validators" - "github.com/ava-labs/avalanchego/snow/validators/validatorstest" - "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/vms/platformvm/warp" -) - -func TestVerifier_Verify(t *testing.T) { - nodeID0 := ids.GenerateTestNodeID() - sk0, err := bls.NewSecretKey() - require.NoError(t, err) - pk0 := bls.PublicFromSecretKey(sk0) - - nodeID1 := ids.GenerateTestNodeID() - sk1, err := bls.NewSecretKey() - require.NoError(t, err) - pk1 := bls.PublicFromSecretKey(sk1) - - networkID := uint32(123) - subnetID := ids.GenerateTestID() - chainID := ids.GenerateTestID() - signer := warp.NewSigner(sk0, networkID, chainID) - - tests := []struct { - name string - - handler p2p.Handler - - ctx context.Context - validators []Validator - threshold uint64 - - pChainState validators.State - pChainHeight uint64 - quorumNum uint64 - quorumDen uint64 - - wantAggregateSignaturesErr error - wantVerifyErr error - }{ - { - name: "passes attestation and verification", - handler: NewHandler(&testAttestor{}, signer, networkID, chainID), - ctx: context.Background(), - validators: []Validator{ - { - NodeID: nodeID0, - PublicKey: pk0, - Weight: 1, - }, - }, - threshold: 1, - pChainState: &validatorstest.State{ - T: t, - GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { - return subnetID, nil - }, - GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - return map[ids.NodeID]*validators.GetValidatorOutput{ - nodeID0: { - NodeID: nodeID0, - PublicKey: pk0, - Weight: 1, - }, - }, nil - }, - }, - quorumNum: 1, - quorumDen: 1, - }, - { - name: "passes attestation and fails verification - insufficient stake", - handler: NewHandler(&testAttestor{}, signer, networkID, chainID), - ctx: context.Background(), - validators: []Validator{ - { - NodeID: nodeID0, - PublicKey: pk0, - Weight: 1, - }, - }, - threshold: 1, - pChainState: &validatorstest.State{ - T: t, - GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { - return subnetID, nil - }, - GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - return map[ids.NodeID]*validators.GetValidatorOutput{ - nodeID0: { - NodeID: nodeID0, - PublicKey: pk0, - Weight: 1, - }, - nodeID1: { - NodeID: nodeID1, - PublicKey: pk1, - Weight: 1, - }, - }, nil - }, - }, - quorumNum: 2, - quorumDen: 2, - wantVerifyErr: warp.ErrInsufficientWeight, - }, - { - name: "fails attestation", - handler: NewHandler( - &testAttestor{Err: errors.New("foobar")}, - signer, - networkID, - chainID, - ), - ctx: context.Background(), - validators: []Validator{ - { - NodeID: nodeID0, - PublicKey: pk0, - Weight: 1, - }, - }, - threshold: 1, - wantAggregateSignaturesErr: ErrInsufficientSignatures, - }, - { - name: "invalid validator set", - ctx: context.Background(), - validators: []Validator{ - { - NodeID: nodeID0, - PublicKey: pk0, - Weight: 1, - }, - { - NodeID: nodeID0, - PublicKey: pk0, - Weight: 1, - }, - }, - wantAggregateSignaturesErr: ErrDuplicateValidator, - }, - { - name: "context canceled", - ctx: func() context.Context { - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - return ctx - }(), - wantAggregateSignaturesErr: context.Canceled, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - - ctx := context.Background() - message, err := warp.NewUnsignedMessage(networkID, chainID, []byte("payload")) - require.NoError(err) - client := p2ptest.NewClient(t, ctx, tt.handler, ids.GenerateTestNodeID(), nodeID0) - verifier := NewSignatureAggregator(logging.NoLog{}, client, 1) - - signedMessage, err := verifier.AggregateSignatures( - tt.ctx, - message, - []byte("justification"), - tt.validators, - tt.threshold, - ) - require.ErrorIs(err, tt.wantAggregateSignaturesErr) - - if signedMessage == nil { - return - } - - err = signedMessage.Signature.Verify( - ctx, - &signedMessage.UnsignedMessage, - networkID, - tt.pChainState, - 0, - tt.quorumNum, - tt.quorumDen, - ) - require.ErrorIs(err, tt.wantVerifyErr) - }) - } -} diff --git a/x/sync/client_test.go b/x/sync/client_test.go index 2633071439da..decc3e20405d 100644 --- a/x/sync/client_test.go +++ b/x/sync/client_test.go @@ -38,12 +38,12 @@ func newDefaultDBConfig() merkledb.Config { } } -func newModifiedRangeProofHandler( +func newFlakyRangeProofHandler( t *testing.T, db merkledb.MerkleDB, modifyResponse func(response *merkledb.RangeProof), ) p2p.Handler { - handler := NewSyncGetRangeProofHandler(logging.NoLog{}, db) + handler := NewGetRangeProofHandler(logging.NoLog{}, db) c := counter{m: 2} return &p2p.TestHandler{ @@ -74,12 +74,12 @@ func newModifiedRangeProofHandler( } } -func newModifiedChangeProofHandler( +func newFlakyChangeProofHandler( t *testing.T, db merkledb.MerkleDB, modifyResponse func(response *merkledb.ChangeProof), ) p2p.Handler { - handler := NewSyncGetChangeProofHandler(logging.NoLog{}, db) + handler := NewGetChangeProofHandler(logging.NoLog{}, db) c := counter{m: 2} return &p2p.TestHandler{ @@ -145,3 +145,14 @@ func (c *counter) Inc() int { c.i++ return result } + +type waitingHandler struct { + p2p.NoOpHandler + handler p2p.Handler + updatedRootChan chan struct{} +} + +func (w *waitingHandler) AppRequest(ctx context.Context, nodeID ids.NodeID, deadline time.Time, requestBytes []byte) ([]byte, *common.AppError) { + <-w.updatedRootChan + return w.handler.AppRequest(ctx, nodeID, deadline, requestBytes) +} diff --git a/x/sync/manager.go b/x/sync/manager.go index dd176c223033..ddcdc1637088 100644 --- a/x/sync/manager.go +++ b/x/sync/manager.go @@ -41,7 +41,7 @@ var ( ErrAlreadyStarted = errors.New("cannot start a Manager that has already been started") ErrAlreadyClosed = errors.New("Manager is closed") ErrNoRangeProofClientProvided = errors.New("range proof client is a required field of the sync config") - ErrNoChangeProofClientProvided = errors.New("change proofclient is a required field of the sync config") + ErrNoChangeProofClientProvided = errors.New("change proof client is a required field of the sync config") ErrNoDatabaseProvided = errors.New("sync database is a required field of the sync config") ErrNoLogProvided = errors.New("log is a required field of the sync config") ErrZeroWorkLimit = errors.New("simultaneous work limit must be greater than 0") @@ -305,7 +305,12 @@ func (m *Manager) doWork(ctx context.Context, work *workItem) { return } - <-time.After(waitTime) + select { + case <-ctx.Done(): + m.finishWorkItem() + return + case <-time.After(waitTime): + } if work.localRootID == ids.Empty { // the keys in this range have not been downloaded, so get all key/values @@ -368,7 +373,8 @@ func (m *Manager) requestChangeProof(ctx context.Context, work *workItem) { defer m.finishWorkItem() if err := m.handleChangeProofResponse(ctx, targetRootID, work, request, responseBytes, err); err != nil { - m.config.Log.Debug("dropping response", zap.Error(err)) + // TODO log responses + m.config.Log.Debug("dropping response", zap.Error(err), zap.Stringer("request", request)) m.retryWork(work) return } @@ -425,7 +431,8 @@ func (m *Manager) requestRangeProof(ctx context.Context, work *workItem) { defer m.finishWorkItem() if err := m.handleRangeProofResponse(ctx, targetRootID, work, request, responseBytes, appErr); err != nil { - m.config.Log.Debug("dropping response", zap.Error(err)) + // TODO log responses + m.config.Log.Debug("dropping response", zap.Error(err), zap.Stringer("request", request)) m.retryWork(work) return } @@ -461,10 +468,11 @@ func (m *Manager) retryWork(work *workItem) { m.workLock.Lock() m.unprocessedWork.Insert(work) m.workLock.Unlock() + m.unprocessedWorkCond.Signal() } // Returns an error if we should drop the response -func (m *Manager) handleResponse( +func (m *Manager) shouldHandleResponse( bytesLimit uint32, responseBytes []byte, err error, @@ -499,7 +507,7 @@ func (m *Manager) handleRangeProofResponse( responseBytes []byte, err error, ) error { - if err := m.handleResponse(request.BytesLimit, responseBytes, err); err != nil { + if err := m.shouldHandleResponse(request.BytesLimit, responseBytes, err); err != nil { return err } @@ -550,7 +558,7 @@ func (m *Manager) handleChangeProofResponse( responseBytes []byte, err error, ) error { - if err := m.handleResponse(request.BytesLimit, responseBytes, err); err != nil { + if err := m.shouldHandleResponse(request.BytesLimit, responseBytes, err); err != nil { return err } @@ -606,7 +614,6 @@ func (m *Manager) handleChangeProofResponse( m.completeWorkItem(ctx, work, largestHandledKey, targetRootID, changeProof.EndProof) case *pb.SyncGetChangeProofResponse_RangeProof: - var rangeProof merkledb.RangeProof if err := rangeProof.UnmarshalProto(changeProofResp.RangeProof); err != nil { return err diff --git a/x/sync/network_server.go b/x/sync/network_server.go index 10d86ed140eb..2153f2fbcc97 100644 --- a/x/sync/network_server.go +++ b/x/sync/network_server.go @@ -49,8 +49,8 @@ var ( errInvalidBounds = errors.New("start key is greater than end key") errInvalidRootHash = fmt.Errorf("root hash must have length %d", hashing.HashLen) - _ p2p.Handler = (*SyncGetChangeProofHandler)(nil) - _ p2p.Handler = (*SyncGetRangeProofHandler)(nil) + _ p2p.Handler = (*GetChangeProofHandler)(nil) + _ p2p.Handler = (*GetRangeProofHandler)(nil) ) func maybeBytesToMaybe(mb *pb.MaybeBytes) maybe.Maybe[[]byte] { @@ -60,21 +60,21 @@ func maybeBytesToMaybe(mb *pb.MaybeBytes) maybe.Maybe[[]byte] { return maybe.Nothing[[]byte]() } -func NewSyncGetChangeProofHandler(log logging.Logger, db DB) *SyncGetChangeProofHandler { - return &SyncGetChangeProofHandler{ +func NewGetChangeProofHandler(log logging.Logger, db DB) *GetChangeProofHandler { + return &GetChangeProofHandler{ log: log, db: db, } } -type SyncGetChangeProofHandler struct { +type GetChangeProofHandler struct { log logging.Logger db DB } -func (*SyncGetChangeProofHandler) AppGossip(context.Context, ids.NodeID, []byte) {} +func (*GetChangeProofHandler) AppGossip(context.Context, ids.NodeID, []byte) {} -func (s *SyncGetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ time.Time, requestBytes []byte) ([]byte, *common.AppError) { +func (g *GetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ time.Time, requestBytes []byte) ([]byte, *common.AppError) { req := &pb.SyncGetChangeProofRequest{} if err := proto.Unmarshal(requestBytes, req); err != nil { return nil, &common.AppError{ @@ -120,6 +120,7 @@ func (s *SyncGetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID if !errors.Is(err, merkledb.ErrInsufficientHistory) { // We should only fail to get a change proof if we have insufficient history. // Other errors are unexpected. + // TODO define custom errors return nil, &common.AppError{ Code: p2p.ErrUnexpected.Code, Message: fmt.Sprintf("failed to get change proof: %s", err), @@ -191,21 +192,21 @@ func (s *SyncGetChangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID } } -func NewSyncGetRangeProofHandler(log logging.Logger, db DB) *SyncGetRangeProofHandler { - return &SyncGetRangeProofHandler{ +func NewGetRangeProofHandler(log logging.Logger, db DB) *GetRangeProofHandler { + return &GetRangeProofHandler{ log: log, db: db, } } -type SyncGetRangeProofHandler struct { +type GetRangeProofHandler struct { log logging.Logger db DB } -func (*SyncGetRangeProofHandler) AppGossip(context.Context, ids.NodeID, []byte) {} +func (*GetRangeProofHandler) AppGossip(context.Context, ids.NodeID, []byte) {} -func (s *SyncGetRangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ time.Time, requestBytes []byte) ([]byte, *common.AppError) { +func (g *GetRangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, _ time.Time, requestBytes []byte) ([]byte, *common.AppError) { req := &pb.SyncGetRangeProofRequest{} if err := proto.Unmarshal(requestBytes, req); err != nil { return nil, &common.AppError{ @@ -227,7 +228,7 @@ func (s *SyncGetRangeProofHandler) AppRequest(ctx context.Context, _ ids.NodeID, proofBytes, err := getRangeProof( ctx, - s.db, + g.db, req, func(rangeProof *merkledb.RangeProof) ([]byte, error) { return proto.Marshal(rangeProof.ToProto()) diff --git a/x/sync/network_server_test.go b/x/sync/network_server_test.go index 84dbd1c12682..c78554cea59f 100644 --- a/x/sync/network_server_test.go +++ b/x/sync/network_server_test.go @@ -85,7 +85,7 @@ func Test_Server_GetRangeProof(t *testing.T) { expectedErr: p2p.ErrUnexpected, }, { - name: "key limit too large", + name: "response bounded by key limit", request: &pb.SyncGetRangeProofRequest{ RootHash: smallTrieRoot[:], KeyLimit: 2 * defaultRequestKeyLimit, @@ -94,7 +94,7 @@ func Test_Server_GetRangeProof(t *testing.T) { expectedResponseLen: defaultRequestKeyLimit, }, { - name: "bytes limit too large", + name: "response bounded by byte limit", request: &pb.SyncGetRangeProofRequest{ RootHash: smallTrieRoot[:], KeyLimit: defaultRequestKeyLimit, @@ -118,7 +118,7 @@ func Test_Server_GetRangeProof(t *testing.T) { t.Run(test.name, func(t *testing.T) { require := require.New(t) - handler := NewSyncGetRangeProofHandler(logging.NoLog{}, smallTrieDB) + handler := NewGetRangeProofHandler(logging.NoLog{}, smallTrieDB) requestBytes, err := proto.Marshal(test.request) require.NoError(err) responseBytes, err := handler.AppRequest(context.Background(), test.nodeID, time.Time{}, requestBytes) @@ -130,17 +130,12 @@ func Test_Server_GetRangeProof(t *testing.T) { require.Nil(responseBytes) return } - require.NotNil(responseBytes) - var proof *merkledb.RangeProof - if !test.proofNil { - var proofProto pb.RangeProof - require.NoError(proto.Unmarshal(responseBytes, &proofProto)) + var proofProto pb.RangeProof + require.NoError(proto.Unmarshal(responseBytes, &proofProto)) - var p merkledb.RangeProof - require.NoError(p.UnmarshalProto(&proofProto)) - proof = &p - } + var proof merkledb.RangeProof + require.NoError(proof.UnmarshalProto(&proofProto)) if test.expectedResponseLen > 0 { require.LessOrEqual(len(proof.KeyValues), test.expectedResponseLen) @@ -344,7 +339,7 @@ func Test_Server_GetChangeProof(t *testing.T) { t.Run(test.name, func(t *testing.T) { require := require.New(t) - handler := NewSyncGetChangeProofHandler(logging.NoLog{}, serverDB) + handler := NewGetChangeProofHandler(logging.NoLog{}, serverDB) requestBytes, err := proto.Marshal(test.request) require.NoError(err) diff --git a/x/sync/sync_test.go b/x/sync/sync_test.go index 7373c08bf5c5..41dd5829a7b8 100644 --- a/x/sync/sync_test.go +++ b/x/sync/sync_test.go @@ -19,13 +19,12 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/network/p2p/p2ptest" - "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/maybe" "github.com/ava-labs/avalanchego/x/merkledb" ) -var _ p2p.Handler = (*testHandler)(nil) +var _ p2p.Handler = (*waitingHandler)(nil) func Test_Creation(t *testing.T) { require := require.New(t) @@ -40,8 +39,8 @@ func Test_Creation(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: &p2p.Client{}, - ChangeProofClient: &p2p.Client{}, + RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), SimultaneousWorkLimit: 5, Log: logging.NoLog{}, BranchFactor: merkledb.BranchFactor16, @@ -73,8 +72,8 @@ func Test_Completion(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, emptyDB), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, emptyDB), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, emptyDB), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, emptyDB), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: emptyRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -178,8 +177,8 @@ func Test_Sync_FindNextKey_InSync(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -254,8 +253,8 @@ func Test_Sync_FindNextKey_Deleted(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: &p2p.Client{}, - ChangeProofClient: &p2p.Client{}, + RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -304,8 +303,8 @@ func Test_Sync_FindNextKey_BranchInLocal(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: &p2p.Client{}, - ChangeProofClient: &p2p.Client{}, + RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: targetRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -341,8 +340,8 @@ func Test_Sync_FindNextKey_BranchInReceived(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: &p2p.Client{}, - ChangeProofClient: &p2p.Client{}, + RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: targetRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -377,8 +376,8 @@ func Test_Sync_FindNextKey_ExtraValues(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -439,8 +438,8 @@ func TestFindNextKeyEmptyEndProof(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: &p2p.Client{}, - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: ids.Empty, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -508,8 +507,8 @@ func Test_Sync_FindNextKey_DifferentChild(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -541,7 +540,6 @@ func Test_Sync_FindNextKey_DifferentChild(t *testing.T) { // Test findNextKey by computing the expected result in a naive, inefficient // way and comparing it to the actual result - func TestFindNextKeyRandom(t *testing.T) { now := time.Now().UnixNano() t.Logf("seed: %d", now) @@ -732,8 +730,8 @@ func TestFindNextKeyRandom(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: localDB, - RangeProofClient: &p2p.Client{}, - ChangeProofClient: &p2p.Client{}, + RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, remoteDB), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, remoteDB), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: ids.GenerateTestID(), SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -775,7 +773,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { { name: "range proof bad response - too many leaves in response", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { response.KeyValues = append(response.KeyValues, merkledb.KeyValue{}) }) @@ -785,7 +783,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { { name: "range proof bad response - removed first key in response", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { response.KeyValues = response.KeyValues[min(1, len(response.KeyValues)):] }) @@ -795,7 +793,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { { name: "range proof bad response - removed first key in response and replaced proof", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { response.KeyValues = response.KeyValues[min(1, len(response.KeyValues)):] response.KeyValues = []merkledb.KeyValue{ { @@ -821,7 +819,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { { name: "range proof bad response - removed key from middle of response", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { i := rand.Intn(max(1, len(response.KeyValues)-1)) // #nosec G404 _ = slices.Delete(response.KeyValues, i, min(len(response.KeyValues), i+1)) }) @@ -832,7 +830,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { { name: "range proof bad response - start and end proof nodes removed", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { response.StartProof = nil response.EndProof = nil }) @@ -843,7 +841,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { { name: "range proof bad response - end proof removed", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { response.EndProof = nil }) @@ -853,7 +851,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { { name: "range proof bad response - empty proof", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newModifiedRangeProofHandler(t, db, func(response *merkledb.RangeProof) { + handler := newFlakyRangeProofHandler(t, db, func(response *merkledb.RangeProof) { response.StartProof = nil response.EndProof = nil response.KeyValues = nil @@ -866,7 +864,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { name: "range proof server flake", rangeProofClient: func(db merkledb.MerkleDB) *p2p.Client { return p2ptest.NewClient(t, context.Background(), &flakyHandler{ - Handler: NewSyncGetRangeProofHandler(logging.NoLog{}, db), + Handler: NewGetRangeProofHandler(logging.NoLog{}, db), c: &counter{m: 2}, }, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, @@ -874,7 +872,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { { name: "change proof bad response - too many keys in response", changeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newModifiedChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { + handler := newFlakyChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { response.KeyChanges = append(response.KeyChanges, make([]merkledb.KeyChange, defaultRequestKeyLimit)...) }) @@ -884,7 +882,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { { name: "change proof bad response - removed first key in response", changeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newModifiedChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { + handler := newFlakyChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { response.KeyChanges = response.KeyChanges[min(1, len(response.KeyChanges)):] }) @@ -894,7 +892,7 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { { name: "change proof bad response - removed key from middle of response", changeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newModifiedChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { + handler := newFlakyChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { i := rand.Intn(max(1, len(response.KeyChanges)-1)) // #nosec G404 _ = slices.Delete(response.KeyChanges, i, min(len(response.KeyChanges), i+1)) }) @@ -903,9 +901,9 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { }, }, { - name: "all proof keys removed from response", + name: "change proof bad response - all proof keys removed from response", changeProofClient: func(db merkledb.MerkleDB) *p2p.Client { - handler := newModifiedChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { + handler := newFlakyChangeProofHandler(t, db, func(response *merkledb.ChangeProof) { response.StartProof = nil response.EndProof = nil }) @@ -914,10 +912,10 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { }, }, { - name: "flaky change proof client", + name: "change proof flaky server", changeProofClient: func(db merkledb.MerkleDB) *p2p.Client { return p2ptest.NewClient(t, context.Background(), &flakyHandler{ - Handler: NewSyncGetChangeProofHandler(logging.NoLog{}, db), + Handler: NewGetChangeProofHandler(logging.NoLog{}, db), c: &counter{m: 2}, }, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) }, @@ -947,13 +945,13 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { changeProofClient *p2p.Client ) - rangeProofHandler := NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync) + rangeProofHandler := NewGetRangeProofHandler(logging.NoLog{}, dbToSync) rangeProofClient = p2ptest.NewClient(t, ctx, rangeProofHandler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) if tt.rangeProofClient != nil { rangeProofClient = tt.rangeProofClient(dbToSync) } - changeProofHandler := NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync) + changeProofHandler := NewGetChangeProofHandler(logging.NoLog{}, dbToSync) changeProofClient = p2ptest.NewClient(t, ctx, changeProofHandler, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) if tt.changeProofClient != nil { changeProofClient = tt.changeProofClient(dbToSync) @@ -976,8 +974,12 @@ func Test_Sync_Result_Correct_Root(t *testing.T) { require.NoError(syncer.Start(ctx)) // Simulate writes on the server - // TODO more than a single write when API is less flaky - for i := 0; i <= 1; i++ { + // + // TODO add more writes when api is not flaky. There is an inherent + // race condition in between writes where UpdateSyncTarget might + // error because it has already reached the sync target before it + // is called. + for i := 0; i < 50; i++ { addkey := make([]byte, r.Intn(50)) _, err = r.Read(addkey) require.NoError(err) @@ -1029,8 +1031,8 @@ func Test_Sync_Result_Correct_Root_With_Sync_Restart(t *testing.T) { ctx := context.Background() syncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -1056,8 +1058,8 @@ func Test_Sync_Result_Correct_Root_With_Sync_Restart(t *testing.T) { newSyncer, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), - ChangeProofClient: p2ptest.NewClient(t, ctx, NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, dbToSync), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: syncRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -1126,13 +1128,13 @@ func Test_Sync_Result_Correct_Root_Update_Root_During(t *testing.T) { updatedRootChan <- struct{}{} ctx := context.Background() - rangeProofClient := p2ptest.NewClient(t, ctx, &testHandler{ - handler: NewSyncGetRangeProofHandler(logging.NoLog{}, dbToSync), + rangeProofClient := p2ptest.NewClient(t, ctx, &waitingHandler{ + handler: NewGetRangeProofHandler(logging.NoLog{}, dbToSync), updatedRootChan: updatedRootChan, }, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) - changeProofClient := p2ptest.NewClient(t, ctx, &testHandler{ - handler: NewSyncGetChangeProofHandler(logging.NoLog{}, dbToSync), + changeProofClient := p2ptest.NewClient(t, ctx, &waitingHandler{ + handler: NewGetChangeProofHandler(logging.NoLog{}, dbToSync), updatedRootChan: updatedRootChan, }, ids.GenerateTestNodeID(), ids.GenerateTestNodeID()) @@ -1182,10 +1184,11 @@ func Test_Sync_UpdateSyncTarget(t *testing.T) { newDefaultDBConfig(), ) require.NoError(err) + ctx := context.Background() m, err := NewManager(ManagerConfig{ DB: db, - RangeProofClient: &p2p.Client{}, - ChangeProofClient: &p2p.Client{}, + RangeProofClient: p2ptest.NewClient(t, ctx, NewGetRangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), + ChangeProofClient: p2ptest.NewClient(t, ctx, NewGetChangeProofHandler(logging.NoLog{}, db), ids.GenerateTestNodeID(), ids.GenerateTestNodeID()), TargetRoot: ids.Empty, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, @@ -1284,14 +1287,3 @@ func generateTrieWithMinKeyLen(t *testing.T, r *rand.Rand, count int, minKeyLen } return db, batch.Write() } - -type testHandler struct { - p2p.NoOpHandler - handler p2p.Handler - updatedRootChan chan struct{} -} - -func (t *testHandler) AppRequest(ctx context.Context, nodeID ids.NodeID, deadline time.Time, requestBytes []byte) ([]byte, *common.AppError) { - <-t.updatedRootChan - return t.handler.AppRequest(ctx, nodeID, deadline, requestBytes) -} From 00fc8d1b578c41484d21b26b1b9a165f8191ee3a Mon Sep 17 00:00:00 2001 From: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:54:09 -0400 Subject: [PATCH 092/199] nit Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- network/acp118/handler.go | 25 ++++++++----------------- network/acp118/handler_test.go | 12 ++++++------ 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/network/acp118/handler.go b/network/acp118/handler.go index e4aae28e3ce6..1a9e210e55d8 100644 --- a/network/acp118/handler.go +++ b/network/acp118/handler.go @@ -21,21 +21,17 @@ var _ p2p.Handler = (*Handler)(nil) // Attestor defines whether to a warp message payload should be attested to type Attestor interface { - Attest(message *warp.UnsignedMessage, justification []byte) (bool, error) + Attest(message *warp.UnsignedMessage, justification []byte) (bool, *common.AppError) } // NewHandler returns an instance of Handler func NewHandler( attestor Attestor, signer warp.Signer, - networkID uint32, - chainID ids.ID, ) *Handler { return &Handler{ - attestor: attestor, - signer: signer, - networkID: networkID, - chainID: chainID, + attestor: attestor, + signer: signer, } } @@ -43,10 +39,8 @@ func NewHandler( type Handler struct { p2p.NoOpHandler - attestor Attestor - signer warp.Signer - networkID uint32 - chainID ids.ID + attestor Attestor + signer warp.Signer } func (h *Handler) AppRequest( @@ -71,12 +65,9 @@ func (h *Handler) AppRequest( } } - ok, err := h.attestor.Attest(msg, request.Justification) - if err != nil { - return nil, &common.AppError{ - Code: p2p.ErrUnexpected.Code, - Message: fmt.Sprintf("failed to attest request: %s", err), - } + ok, appErr := h.attestor.Attest(msg, request.Justification) + if appErr != nil { + return nil, appErr } if !ok { diff --git a/network/acp118/handler_test.go b/network/acp118/handler_test.go index 77af9e8dd0fb..c3a9a7de96ee 100644 --- a/network/acp118/handler_test.go +++ b/network/acp118/handler_test.go @@ -5,7 +5,6 @@ package acp118 import ( "context" - "errors" "testing" "github.com/stretchr/testify/require" @@ -15,6 +14,7 @@ import ( "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/network/p2p/p2ptest" "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/platformvm/warp" @@ -31,8 +31,8 @@ func TestHandler(t *testing.T) { }{ { name: "signature fails attestation", - attestor: &testAttestor{Err: errors.New("foo")}, - expectedErr: p2p.ErrUnexpected, + attestor: &testAttestor{Err: &common.AppError{Code: int32(123)}}, + expectedErr: &common.AppError{Code: int32(123)}, }, { name: "signature not attested", @@ -57,7 +57,7 @@ func TestHandler(t *testing.T) { networkID := uint32(123) chainID := ids.GenerateTestID() signer := warp.NewSigner(sk, networkID, chainID) - h := NewHandler(tt.attestor, signer, networkID, chainID) + h := NewHandler(tt.attestor, signer) clientNodeID := ids.GenerateTestNodeID() serverNodeID := ids.GenerateTestNodeID() c := p2ptest.NewClient( @@ -110,9 +110,9 @@ func TestHandler(t *testing.T) { // The zero value of testAttestor attests type testAttestor struct { CantAttest bool - Err error + Err *common.AppError } -func (t testAttestor) Attest(*warp.UnsignedMessage, []byte) (bool, error) { +func (t testAttestor) Attest(*warp.UnsignedMessage, []byte) (bool, *common.AppError) { return !t.CantAttest, t.Err } From c08a1d71d0245fd36508f2a1ece8824f1beddf49 Mon Sep 17 00:00:00 2001 From: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:55:40 -0400 Subject: [PATCH 093/199] undo Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- network/p2p/p2ptest/client.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/network/p2p/p2ptest/client.go b/network/p2p/p2ptest/client.go index 8d41d6b99bce..b75654028666 100644 --- a/network/p2p/p2ptest/client.go +++ b/network/p2p/p2ptest/client.go @@ -23,7 +23,7 @@ import ( // communicate with a server with the specified handler func NewClient( t *testing.T, - rootCtx context.Context, + ctx context.Context, handler p2p.Handler, clientNodeID ids.NodeID, serverNodeID ids.NodeID, @@ -38,6 +38,8 @@ func NewClient( require.NoError(t, err) clientSender.SendAppGossipF = func(ctx context.Context, _ common.SendConfig, gossipBytes []byte) error { + // Send the request asynchronously to avoid deadlock when the server + // sends the response back to the client go func() { require.NoError(t, serverNetwork.AppGossip(ctx, clientNodeID, gossipBytes)) }() @@ -56,6 +58,8 @@ func NewClient( } serverSender.SendAppResponseF = func(ctx context.Context, _ ids.NodeID, requestID uint32, responseBytes []byte) error { + // Send the request asynchronously to avoid deadlock when the server + // sends the response back to the client go func() { require.NoError(t, clientNetwork.AppResponse(ctx, serverNodeID, requestID, responseBytes)) }() @@ -64,6 +68,8 @@ func NewClient( } serverSender.SendAppErrorF = func(ctx context.Context, _ ids.NodeID, requestID uint32, errorCode int32, errorMessage string) error { + // Send the request asynchronously to avoid deadlock when the server + // sends the response back to the client go func() { require.NoError(t, clientNetwork.AppRequestFailed(ctx, serverNodeID, requestID, &common.AppError{ Code: errorCode, @@ -74,10 +80,10 @@ func NewClient( return nil } - require.NoError(t, clientNetwork.Connected(rootCtx, clientNodeID, nil)) - require.NoError(t, clientNetwork.Connected(rootCtx, serverNodeID, nil)) - require.NoError(t, serverNetwork.Connected(rootCtx, clientNodeID, nil)) - require.NoError(t, serverNetwork.Connected(rootCtx, serverNodeID, nil)) + require.NoError(t, clientNetwork.Connected(ctx, clientNodeID, nil)) + require.NoError(t, clientNetwork.Connected(ctx, serverNodeID, nil)) + require.NoError(t, serverNetwork.Connected(ctx, clientNodeID, nil)) + require.NoError(t, serverNetwork.Connected(ctx, serverNodeID, nil)) require.NoError(t, serverNetwork.AddHandler(0, handler)) return clientNetwork.NewClient(0) From 9d07a45869d2a90bc54f3592ea6c178680f23ae8 Mon Sep 17 00:00:00 2001 From: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:57:31 -0400 Subject: [PATCH 094/199] nit Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- network/acp118/handler.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/network/acp118/handler.go b/network/acp118/handler.go index 1a9e210e55d8..1a0c43bb0bc6 100644 --- a/network/acp118/handler.go +++ b/network/acp118/handler.go @@ -25,10 +25,7 @@ type Attestor interface { } // NewHandler returns an instance of Handler -func NewHandler( - attestor Attestor, - signer warp.Signer, -) *Handler { +func NewHandler(attestor Attestor, signer warp.Signer) *Handler { return &Handler{ attestor: attestor, signer: signer, From f91e0a89611e137eb54d7da46e1a4478a7fe6877 Mon Sep 17 00:00:00 2001 From: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:02:48 -0400 Subject: [PATCH 095/199] nit Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- network/acp118/handler.go | 11 +++-------- network/acp118/handler_test.go | 13 +++---------- network/p2p/error.go | 6 ------ 3 files changed, 6 insertions(+), 24 deletions(-) diff --git a/network/acp118/handler.go b/network/acp118/handler.go index 1a0c43bb0bc6..0fb4dd6185b6 100644 --- a/network/acp118/handler.go +++ b/network/acp118/handler.go @@ -21,7 +21,7 @@ var _ p2p.Handler = (*Handler)(nil) // Attestor defines whether to a warp message payload should be attested to type Attestor interface { - Attest(message *warp.UnsignedMessage, justification []byte) (bool, *common.AppError) + Attest(message *warp.UnsignedMessage, justification []byte) *common.AppError } // NewHandler returns an instance of Handler @@ -62,13 +62,8 @@ func (h *Handler) AppRequest( } } - ok, appErr := h.attestor.Attest(msg, request.Justification) - if appErr != nil { - return nil, appErr - } - - if !ok { - return nil, p2p.ErrAttestFailed + if err := h.attestor.Attest(msg, request.Justification); err != nil { + return nil, err } signature, err := h.signer.Sign(msg) diff --git a/network/acp118/handler_test.go b/network/acp118/handler_test.go index c3a9a7de96ee..0897e74e39c7 100644 --- a/network/acp118/handler_test.go +++ b/network/acp118/handler_test.go @@ -11,7 +11,6 @@ import ( "google.golang.org/protobuf/proto" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/network/p2p/p2ptest" "github.com/ava-labs/avalanchego/proto/pb/sdk" "github.com/ava-labs/avalanchego/snow/engine/common" @@ -34,11 +33,6 @@ func TestHandler(t *testing.T) { attestor: &testAttestor{Err: &common.AppError{Code: int32(123)}}, expectedErr: &common.AppError{Code: int32(123)}, }, - { - name: "signature not attested", - attestor: &testAttestor{CantAttest: true}, - expectedErr: p2p.ErrAttestFailed, - }, { name: "signature attested", attestor: &testAttestor{}, @@ -109,10 +103,9 @@ func TestHandler(t *testing.T) { // The zero value of testAttestor attests type testAttestor struct { - CantAttest bool - Err *common.AppError + Err *common.AppError } -func (t testAttestor) Attest(*warp.UnsignedMessage, []byte) (bool, *common.AppError) { - return !t.CantAttest, t.Err +func (t testAttestor) Attest(*warp.UnsignedMessage, []byte) *common.AppError { + return t.Err } diff --git a/network/p2p/error.go b/network/p2p/error.go index 67b0317153e6..07207319a041 100644 --- a/network/p2p/error.go +++ b/network/p2p/error.go @@ -30,10 +30,4 @@ var ( Code: -4, Message: "throttled", } - // ErrAttestFailed should be used to indicate that a request failed - // to be signed due to the peer being unable to attest the message - ErrAttestFailed = &common.AppError{ - Code: -5, - Message: "failed attestation", - } ) From a35b09089f30c988ddceb47ecee87becfdae2ddd Mon Sep 17 00:00:00 2001 From: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:08:02 -0400 Subject: [PATCH 096/199] add context Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- network/acp118/handler.go | 10 +++++++--- network/acp118/handler_test.go | 6 +++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/network/acp118/handler.go b/network/acp118/handler.go index 0fb4dd6185b6..1dc9ef59fb40 100644 --- a/network/acp118/handler.go +++ b/network/acp118/handler.go @@ -21,7 +21,11 @@ var _ p2p.Handler = (*Handler)(nil) // Attestor defines whether to a warp message payload should be attested to type Attestor interface { - Attest(message *warp.UnsignedMessage, justification []byte) *common.AppError + Attest( + ctx context.Context, + message *warp.UnsignedMessage, + justification []byte, + ) *common.AppError } // NewHandler returns an instance of Handler @@ -41,7 +45,7 @@ type Handler struct { } func (h *Handler) AppRequest( - _ context.Context, + ctx context.Context, _ ids.NodeID, _ time.Time, requestBytes []byte, @@ -62,7 +66,7 @@ func (h *Handler) AppRequest( } } - if err := h.attestor.Attest(msg, request.Justification); err != nil { + if err := h.attestor.Attest(ctx, msg, request.Justification); err != nil { return nil, err } diff --git a/network/acp118/handler_test.go b/network/acp118/handler_test.go index 0897e74e39c7..c5cc827bc1e3 100644 --- a/network/acp118/handler_test.go +++ b/network/acp118/handler_test.go @@ -106,6 +106,10 @@ type testAttestor struct { Err *common.AppError } -func (t testAttestor) Attest(*warp.UnsignedMessage, []byte) *common.AppError { +func (t testAttestor) Attest( + context.Context, + *warp.UnsignedMessage, + []byte, +) *common.AppError { return t.Err } From dd7029cd7564581ae7f2806ba9237a1fa4f714c1 Mon Sep 17 00:00:00 2001 From: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:08:18 -0400 Subject: [PATCH 097/199] fix Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- network/acp118/handler_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/acp118/handler_test.go b/network/acp118/handler_test.go index c5cc827bc1e3..6a1f866851f6 100644 --- a/network/acp118/handler_test.go +++ b/network/acp118/handler_test.go @@ -95,7 +95,7 @@ func TestHandler(t *testing.T) { require.Equal(tt.expectedVerify, bls.Verify(pk, signature, request.Message)) } - require.NoError(c.AppRequest(ctx, set.Of(serverNodeID), requestBytes, onResponse)) + require.NoError(c.AppRequest(ctx, set.Of(clientNodeID), requestBytes, onResponse)) <-done }) } From 8978452302048f2513647c6455b797819b3d9e1c Mon Sep 17 00:00:00 2001 From: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:11:25 -0400 Subject: [PATCH 098/199] rename attestor -> verifier Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- network/acp118/handler.go | 14 +++++++------- network/acp118/handler_test.go | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/network/acp118/handler.go b/network/acp118/handler.go index 1dc9ef59fb40..058c5af9dbfb 100644 --- a/network/acp118/handler.go +++ b/network/acp118/handler.go @@ -19,9 +19,9 @@ import ( var _ p2p.Handler = (*Handler)(nil) -// Attestor defines whether to a warp message payload should be attested to -type Attestor interface { - Attest( +// Verifier defines whether a warp message payload should be verified +type Verifier interface { + Verify( ctx context.Context, message *warp.UnsignedMessage, justification []byte, @@ -29,9 +29,9 @@ type Attestor interface { } // NewHandler returns an instance of Handler -func NewHandler(attestor Attestor, signer warp.Signer) *Handler { +func NewHandler(verifier Verifier, signer warp.Signer) *Handler { return &Handler{ - attestor: attestor, + verifier: verifier, signer: signer, } } @@ -40,7 +40,7 @@ func NewHandler(attestor Attestor, signer warp.Signer) *Handler { type Handler struct { p2p.NoOpHandler - attestor Attestor + verifier Verifier signer warp.Signer } @@ -66,7 +66,7 @@ func (h *Handler) AppRequest( } } - if err := h.attestor.Attest(ctx, msg, request.Justification); err != nil { + if err := h.verifier.Verify(ctx, msg, request.Justification); err != nil { return nil, err } diff --git a/network/acp118/handler_test.go b/network/acp118/handler_test.go index 6a1f866851f6..bdf76f3b942c 100644 --- a/network/acp118/handler_test.go +++ b/network/acp118/handler_test.go @@ -19,23 +19,23 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/warp" ) -var _ Attestor = (*testAttestor)(nil) +var _ Verifier = (*testVerifier)(nil) func TestHandler(t *testing.T) { tests := []struct { name string - attestor Attestor + verifier Verifier expectedErr error expectedVerify bool }{ { - name: "signature fails attestation", - attestor: &testAttestor{Err: &common.AppError{Code: int32(123)}}, + name: "signature fails verification", + verifier: &testVerifier{Err: &common.AppError{Code: int32(123)}}, expectedErr: &common.AppError{Code: int32(123)}, }, { - name: "signature attested", - attestor: &testAttestor{}, + name: "signature signed", + verifier: &testVerifier{}, expectedVerify: true, }, } @@ -51,7 +51,7 @@ func TestHandler(t *testing.T) { networkID := uint32(123) chainID := ids.GenerateTestID() signer := warp.NewSigner(sk, networkID, chainID) - h := NewHandler(tt.attestor, signer) + h := NewHandler(tt.verifier, signer) clientNodeID := ids.GenerateTestNodeID() serverNodeID := ids.GenerateTestNodeID() c := p2ptest.NewClient( @@ -101,12 +101,12 @@ func TestHandler(t *testing.T) { } } -// The zero value of testAttestor attests -type testAttestor struct { +// The zero value of testVerifier verifies +type testVerifier struct { Err *common.AppError } -func (t testAttestor) Attest( +func (t testVerifier) Verify( context.Context, *warp.UnsignedMessage, []byte, From a3832095128c0fe69e64da579f1e91b668e5a2e7 Mon Sep 17 00:00:00 2001 From: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:21:23 -0400 Subject: [PATCH 099/199] nit Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- network/{ => p2p}/acp118/handler.go | 0 network/{ => p2p}/acp118/handler_test.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename network/{ => p2p}/acp118/handler.go (100%) rename network/{ => p2p}/acp118/handler_test.go (100%) diff --git a/network/acp118/handler.go b/network/p2p/acp118/handler.go similarity index 100% rename from network/acp118/handler.go rename to network/p2p/acp118/handler.go diff --git a/network/acp118/handler_test.go b/network/p2p/acp118/handler_test.go similarity index 100% rename from network/acp118/handler_test.go rename to network/p2p/acp118/handler_test.go From 8b52eadf9434ad162f2661702072c26e604bc0f9 Mon Sep 17 00:00:00 2001 From: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:25:35 -0400 Subject: [PATCH 100/199] nit Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> --- network/p2p/acp118/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/p2p/acp118/handler.go b/network/p2p/acp118/handler.go index 058c5af9dbfb..8fb39b8a5513 100644 --- a/network/p2p/acp118/handler.go +++ b/network/p2p/acp118/handler.go @@ -19,7 +19,7 @@ import ( var _ p2p.Handler = (*Handler)(nil) -// Verifier defines whether a warp message payload should be verified +// Verifier verifies that a warp message should be signed type Verifier interface { Verify( ctx context.Context, From 01afe2a165c48dd2ecb5c27d2367196511213ac2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 1 Oct 2024 14:17:54 -0400 Subject: [PATCH 101/199] Fix compilation --- vms/platformvm/txs/executor/standard_tx_executor.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index b2a0895ed263..4b429eac8db3 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -664,6 +664,9 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal if err != nil { return err } + if err := msg.Verify(); err != nil { + return err + } expectedChainID, expectedAddress, err := e.State.GetSubnetManager(msg.SubnetID) if err != nil { @@ -712,6 +715,11 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return fmt.Errorf("expiry for %s already exists", validationID) } + nodeID, err := ids.ToNodeID(msg.NodeID) + if err != nil { + return err + } + balanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &tx.RemainingBalanceOwner) if err != nil { return err @@ -720,7 +728,7 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal sov := state.SubnetOnlyValidator{ ValidationID: validationID, SubnetID: msg.SubnetID, - NodeID: msg.NodeID, + NodeID: nodeID, PublicKey: bls.PublicKeyToUncompressedBytes(pop.Key()), RemainingBalanceOwner: balanceOwner, StartTime: currentTimestampUnix, From ab6bb9dce639fbc4709d2726afce6a4afc9e55a6 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 1 Oct 2024 14:19:44 -0400 Subject: [PATCH 102/199] Fix compilation --- vms/platformvm/txs/executor/standard_tx_executor.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 38205aed2977..77e1eaf1d1c4 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -8,7 +8,6 @@ import ( "context" "errors" "fmt" - "math" "time" "go.uber.org/zap" @@ -816,12 +815,12 @@ func (e *StandardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat return err } - msg, err := message.ParseSetSubnetValidatorWeight(addressedCall.Payload) + msg, err := message.ParseSubnetValidatorWeight(addressedCall.Payload) if err != nil { return err } - if msg.Nonce == math.MaxUint64 && msg.Weight != 0 { - return fmt.Errorf("setting nonce to %d can only be done when removing the validator", msg.Nonce) + if err := msg.Verify(); err != nil { + return err } sov, err := e.State.GetSubnetOnlyValidator(msg.ValidationID) From 660f1b279bd65df3d2f4c358b679ddb990c68d8d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 1 Oct 2024 14:32:12 -0400 Subject: [PATCH 103/199] Use acp118 helper --- vms/platformvm/network/network.go | 5 +- vms/platformvm/network/warp.go | 115 ++++++++---------------------- 2 files changed, 33 insertions(+), 87 deletions(-) diff --git a/vms/platformvm/network/network.go b/vms/platformvm/network/network.go index d4780646a0ac..782d6d3273fc 100644 --- a/vms/platformvm/network/network.go +++ b/vms/platformvm/network/network.go @@ -14,6 +14,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/p2p/acp118" "github.com/ava-labs/avalanchego/network/p2p/gossip" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/validators" @@ -164,11 +165,11 @@ func New( } // We allow all peers to request warp messaging signatures - signatureRequestHandler := signatureRequestHandler{ + signatureRequestVerifier := signatureRequestVerifier{ stateLock: stateLock, state: state, - signer: signer, } + signatureRequestHandler := acp118.NewHandler(signatureRequestVerifier, signer) if err := p2pNetwork.AddHandler(p2p.SignatureRequestHandlerID, signatureRequestHandler); err != nil { return nil, err diff --git a/vms/platformvm/network/warp.go b/vms/platformvm/network/warp.go index 6147fdb2a1f6..3fb9520855df 100644 --- a/vms/platformvm/network/warp.go +++ b/vms/platformvm/network/warp.go @@ -8,16 +8,10 @@ import ( "fmt" "math" "sync" - "time" - - "google.golang.org/protobuf/proto" "github.com/ava-labs/avalanchego/database" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/network/p2p" - "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/network/p2p/acp118" "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" @@ -25,68 +19,45 @@ import ( ) const ( - ErrFailedToParseRequest = iota + 1 - ErrFailedToParseWarp - ErrFailedToParseWarpAddressedCall + ErrFailedToParseWarpAddressedCall = iota + 1 ErrWarpAddressedCallHasSourceAddress ErrFailedToParseWarpAddressedCallPayload ErrUnsupportedWarpAddressedCallPayloadType + ErrFailedToParseJustification ErrMismatchedValidationID ErrValidationDoesNotExist ErrValidationExists ErrValidationCouldBeRegistered + ErrImpossibleNonce ErrWrongNonce ErrWrongWeight - ErrFailedToSignWarp + errUnimplemented // TODO: Remove ) -var _ p2p.Handler = (*signatureRequestHandler)(nil) - -type signatureRequestHandler struct { - p2p.NoOpHandler +var _ acp118.Verifier = (*signatureRequestVerifier)(nil) +type signatureRequestVerifier struct { stateLock sync.Locker state state.Chain - - signer warp.Signer } -func (s signatureRequestHandler) AppRequest( +func (s signatureRequestVerifier) Verify( _ context.Context, - _ ids.NodeID, - _ time.Time, - requestBytes []byte, -) ([]byte, *common.AppError) { - // Per ACP-118, the requestBytes are the serialized form of - // sdk.SignatureRequest. - var req sdk.SignatureRequest - if err := proto.Unmarshal(requestBytes, &req); err != nil { - return nil, &common.AppError{ - Code: ErrFailedToParseRequest, - Message: "failed to unmarshal request: " + err.Error(), - } - } - - unsignedMessage, err := warp.ParseUnsignedMessage(req.Message) - if err != nil { - return nil, &common.AppError{ - Code: ErrFailedToParseWarp, - Message: "failed to parse warp message: " + err.Error(), - } - } - + unsignedMessage *warp.UnsignedMessage, + justification []byte, +) *common.AppError { msg, err := payload.ParseAddressedCall(unsignedMessage.Payload) if err != nil { - return nil, &common.AppError{ + return &common.AppError{ Code: ErrFailedToParseWarpAddressedCall, Message: "failed to parse warp addressed call: " + err.Error(), } } if len(msg.SourceAddress) != 0 { - return nil, &common.AppError{ + return &common.AppError{ Code: ErrWarpAddressedCallHasSourceAddress, Message: "source address should be empty", } @@ -94,54 +65,28 @@ func (s signatureRequestHandler) AppRequest( payloadIntf, err := message.Parse(msg.Payload) if err != nil { - return nil, &common.AppError{ + return &common.AppError{ Code: ErrFailedToParseWarpAddressedCallPayload, Message: "failed to parse warp addressed call payload: " + err.Error(), } } - var payloadErr *common.AppError switch payload := payloadIntf.(type) { case *message.SubnetConversion: - payloadErr = s.verifySubnetConversion(payload, req.Justification) + return s.verifySubnetConversion(payload, justification) case *message.SubnetValidatorRegistration: - payloadErr = s.verifySubnetValidatorRegistration(payload, req.Justification) + return s.verifySubnetValidatorRegistration(payload, justification) case *message.SubnetValidatorWeight: - payloadErr = s.verifySubnetValidatorWeight(payload) + return s.verifySubnetValidatorWeight(payload) default: - payloadErr = &common.AppError{ + return &common.AppError{ Code: ErrUnsupportedWarpAddressedCallPayloadType, Message: fmt.Sprintf("unsupported warp addressed call payload type: %T", payloadIntf), } } - if payloadErr != nil { - return nil, payloadErr - } - - sig, err := s.signer.Sign(unsignedMessage) - if err != nil { - return nil, &common.AppError{ - Code: ErrFailedToSignWarp, - Message: "failed to sign warp message: " + err.Error(), - } - } - - // Per ACP-118, the responseBytes are the serialized form of - // sdk.SignatureResponse. - resp := &sdk.SignatureResponse{ - Signature: sig, - } - respBytes, err := proto.Marshal(resp) - if err != nil { - return nil, &common.AppError{ - Code: common.ErrUndefined.Code, - Message: "failed to marshal response: " + err.Error(), - } - } - return respBytes, nil } -func (s signatureRequestHandler) verifySubnetConversion( +func (s signatureRequestVerifier) verifySubnetConversion( msg *message.SubnetConversion, justification []byte, ) *common.AppError { @@ -154,18 +99,10 @@ func (s signatureRequestHandler) verifySubnetConversion( } } -func (s signatureRequestHandler) verifySubnetValidatorRegistration( +func (s signatureRequestVerifier) verifySubnetValidatorRegistration( msg *message.SubnetValidatorRegistration, justification []byte, ) *common.AppError { - var justificationID ids.ID = hashing.ComputeHash256Array(justification) - if msg.ValidationID != justificationID { - return &common.AppError{ - Code: ErrMismatchedValidationID, - Message: fmt.Sprintf("validationID %q != justificationID %q", msg.ValidationID, justificationID), - } - } - registerSubnetValidator, err := message.ParseRegisterSubnetValidator(justification) if err != nil { return &common.AppError{ @@ -174,6 +111,14 @@ func (s signatureRequestHandler) verifySubnetValidatorRegistration( } } + justificationID := registerSubnetValidator.ValidationID() + if msg.ValidationID != justificationID { + return &common.AppError{ + Code: ErrMismatchedValidationID, + Message: fmt.Sprintf("validationID %q != justificationID %q", msg.ValidationID, justificationID), + } + } + s.stateLock.Lock() defer s.stateLock.Unlock() @@ -236,7 +181,7 @@ func (s signatureRequestHandler) verifySubnetValidatorRegistration( return nil // The validator has been removed } -func (s signatureRequestHandler) verifySubnetValidatorWeight( +func (s signatureRequestVerifier) verifySubnetValidatorWeight( msg *message.SubnetValidatorWeight, ) *common.AppError { if msg.Nonce == math.MaxUint64 { @@ -268,7 +213,7 @@ func (s signatureRequestHandler) verifySubnetValidatorWeight( } case msg.Weight != sov.Weight: return &common.AppError{ - Code: ErrWrongNonce, + Code: ErrWrongWeight, Message: fmt.Sprintf("provided weight %d != expected weight %d", msg.Weight, sov.Weight), } default: From 02625cb85e6d7fb4b62d1d45ad4b7e93f75ff46f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 1 Oct 2024 14:34:52 -0400 Subject: [PATCH 104/199] Add const alias --- network/p2p/acp118/handler.go | 2 ++ vms/platformvm/network/network.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/network/p2p/acp118/handler.go b/network/p2p/acp118/handler.go index ebceefb1fb85..706476772598 100644 --- a/network/p2p/acp118/handler.go +++ b/network/p2p/acp118/handler.go @@ -17,6 +17,8 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/warp" ) +const HandlerID = p2p.SignatureRequestHandlerID + var _ p2p.Handler = (*Handler)(nil) // Verifier verifies that a warp message should be signed diff --git a/vms/platformvm/network/network.go b/vms/platformvm/network/network.go index 782d6d3273fc..9035b90f03f8 100644 --- a/vms/platformvm/network/network.go +++ b/vms/platformvm/network/network.go @@ -171,7 +171,7 @@ func New( } signatureRequestHandler := acp118.NewHandler(signatureRequestVerifier, signer) - if err := p2pNetwork.AddHandler(p2p.SignatureRequestHandlerID, signatureRequestHandler); err != nil { + if err := p2pNetwork.AddHandler(acp118.HandlerID, signatureRequestHandler); err != nil { return nil, err } From ddb9ba6d72b7957d3374e20f4ef7a5dcc333cc32 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 1 Oct 2024 15:11:05 -0400 Subject: [PATCH 105/199] Add deactivation owner --- vms/platformvm/state/subnet_only_validator.go | 5 +++++ .../state/subnet_only_validator_test.go | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 9b505cb22842..487471a5f2b7 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -76,6 +76,10 @@ type SubnetOnlyValidator struct { // balance of the validator after removing accrued fees. RemainingBalanceOwner []byte `serialize:"true"` + // DeactivationOwner is the owner that can manually deactivate the + // validator. + DeactivationOwner []byte `serialize:"true"` + // StartTime is the unix timestamp, in seconds, when this validator was // added to the set. StartTime uint64 `serialize:"true"` @@ -128,6 +132,7 @@ func (v SubnetOnlyValidator) validateConstants(o SubnetOnlyValidator) bool { v.NodeID == o.NodeID && bytes.Equal(v.PublicKey, o.PublicKey) && bytes.Equal(v.RemainingBalanceOwner, o.RemainingBalanceOwner) && + bytes.Equal(v.DeactivationOwner, o.DeactivationOwner) && v.StartTime == o.StartTime } diff --git a/vms/platformvm/state/subnet_only_validator_test.go b/vms/platformvm/state/subnet_only_validator_test.go index e76c4b73a242..3ebf754382d9 100644 --- a/vms/platformvm/state/subnet_only_validator_test.go +++ b/vms/platformvm/state/subnet_only_validator_test.go @@ -82,6 +82,7 @@ func TestSubnetOnlyValidator_validateConstants(t *testing.T) { NodeID: ids.GenerateTestNodeID(), PublicKey: utils.RandomBytes(bls.PublicKeyLen), RemainingBalanceOwner: utils.RandomBytes(32), + DeactivationOwner: utils.RandomBytes(32), StartTime: rand.Uint64(), // #nosec G404 } @@ -103,6 +104,7 @@ func TestSubnetOnlyValidator_validateConstants(t *testing.T) { NodeID: ids.GenerateTestNodeID(), PublicKey: utils.RandomBytes(bls.PublicKeyLen), RemainingBalanceOwner: utils.RandomBytes(32), + DeactivationOwner: utils.RandomBytes(32), StartTime: rand.Uint64(), // #nosec G404 }, expected: true, @@ -115,6 +117,7 @@ func TestSubnetOnlyValidator_validateConstants(t *testing.T) { NodeID: sov.NodeID, PublicKey: sov.PublicKey, RemainingBalanceOwner: sov.RemainingBalanceOwner, + DeactivationOwner: sov.DeactivationOwner, StartTime: sov.StartTime, }, }, @@ -126,6 +129,7 @@ func TestSubnetOnlyValidator_validateConstants(t *testing.T) { NodeID: ids.GenerateTestNodeID(), PublicKey: sov.PublicKey, RemainingBalanceOwner: sov.RemainingBalanceOwner, + DeactivationOwner: sov.DeactivationOwner, StartTime: sov.StartTime, }, }, @@ -137,6 +141,7 @@ func TestSubnetOnlyValidator_validateConstants(t *testing.T) { NodeID: sov.NodeID, PublicKey: utils.RandomBytes(bls.PublicKeyLen), RemainingBalanceOwner: sov.RemainingBalanceOwner, + DeactivationOwner: sov.DeactivationOwner, StartTime: sov.StartTime, }, }, @@ -148,6 +153,19 @@ func TestSubnetOnlyValidator_validateConstants(t *testing.T) { NodeID: sov.NodeID, PublicKey: sov.PublicKey, RemainingBalanceOwner: utils.RandomBytes(32), + DeactivationOwner: sov.DeactivationOwner, + StartTime: sov.StartTime, + }, + }, + { + name: "different deactivationOwner", + v: SubnetOnlyValidator{ + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: sov.PublicKey, + RemainingBalanceOwner: sov.RemainingBalanceOwner, + DeactivationOwner: utils.RandomBytes(32), StartTime: sov.StartTime, }, }, @@ -159,6 +177,7 @@ func TestSubnetOnlyValidator_validateConstants(t *testing.T) { NodeID: sov.NodeID, PublicKey: sov.PublicKey, RemainingBalanceOwner: sov.RemainingBalanceOwner, + DeactivationOwner: sov.DeactivationOwner, StartTime: rand.Uint64(), // #nosec G404 }, }, From 556f1ebf3ffb704b03ad241b5b06189bb80ed508 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 1 Oct 2024 15:28:04 -0400 Subject: [PATCH 106/199] fix tests --- vms/platformvm/state/state_test.go | 258 +++++++++--------- .../state/subnet_only_validator_test.go | 16 +- 2 files changed, 135 insertions(+), 139 deletions(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index f093b90a587c..d0db7bd6d11c 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1424,13 +1424,12 @@ func TestSubnetOnlyValidators(t *testing.T) { name: "initially active not modified", initial: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active }, }, }, @@ -1438,13 +1437,12 @@ func TestSubnetOnlyValidators(t *testing.T) { name: "initially inactive not modified", initial: []SubnetOnlyValidator{ { - ValidationID: ids.GenerateTestID(), - SubnetID: ids.GenerateTestID(), - NodeID: ids.GenerateTestNodeID(), - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 1, // Not removed - EndAccumulatedFee: 0, // Inactive + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: pkBytes, + Weight: 1, // Not removed + EndAccumulatedFee: 0, // Inactive }, }, }, @@ -1452,23 +1450,21 @@ func TestSubnetOnlyValidators(t *testing.T) { name: "initially active removed", initial: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active }, }, sovs: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 0, // Removed + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 0, // Removed }, }, }, @@ -1476,23 +1472,21 @@ func TestSubnetOnlyValidators(t *testing.T) { name: "initially inactive removed", initial: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 1, // Not removed - EndAccumulatedFee: 0, // Inactive + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 1, // Not removed + EndAccumulatedFee: 0, // Inactive }, }, sovs: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 0, // Removed + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 0, // Removed }, }, }, @@ -1500,24 +1494,22 @@ func TestSubnetOnlyValidators(t *testing.T) { name: "increase active weight", initial: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active }, }, sovs: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 2, // Increased - EndAccumulatedFee: 1, // Active + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 2, // Increased + EndAccumulatedFee: 1, // Active }, }, }, @@ -1525,24 +1517,22 @@ func TestSubnetOnlyValidators(t *testing.T) { name: "deactivate", initial: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active }, }, sovs: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 1, // Not removed - EndAccumulatedFee: 0, // Inactive + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 1, // Not removed + EndAccumulatedFee: 0, // Inactive }, }, }, @@ -1550,24 +1540,22 @@ func TestSubnetOnlyValidators(t *testing.T) { name: "reactivate", initial: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 1, // Not removed - EndAccumulatedFee: 0, // Inactive + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 1, // Not removed + EndAccumulatedFee: 0, // Inactive }, }, sovs: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active }, }, }, @@ -1575,33 +1563,30 @@ func TestSubnetOnlyValidators(t *testing.T) { name: "update multiple times", initial: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active }, }, sovs: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 2, // Not removed - EndAccumulatedFee: 1, // Inactive + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 2, // Not removed + EndAccumulatedFee: 1, // Inactive }, { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 3, // Not removed - EndAccumulatedFee: 1, // Inactive + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 3, // Not removed + EndAccumulatedFee: 1, // Inactive }, }, }, @@ -1609,32 +1594,29 @@ func TestSubnetOnlyValidators(t *testing.T) { name: "change validationID", initial: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active }, }, sovs: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 0, // Removed + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 0, // Removed }, { - ValidationID: ids.GenerateTestID(), - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: otherPKBytes, - RemainingBalanceOwner: []byte{}, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Inactive + ValidationID: ids.GenerateTestID(), + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: otherPKBytes, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Inactive }, }, }, @@ -1642,21 +1624,19 @@ func TestSubnetOnlyValidators(t *testing.T) { name: "added and removed", sovs: []SubnetOnlyValidator{ { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 1, // Not removed - EndAccumulatedFee: 1, // Active + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 1, // Not removed + EndAccumulatedFee: 1, // Active }, { - ValidationID: sov.ValidationID, - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: pkBytes, - RemainingBalanceOwner: []byte{}, - Weight: 0, // Removed + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 0, // Removed }, }, }, @@ -1674,6 +1654,9 @@ func TestSubnetOnlyValidators(t *testing.T) { subnetIDs set.Set[ids.ID] ) for _, sov := range test.initial { + sov.RemainingBalanceOwner = []byte{} + sov.DeactivationOwner = []byte{} + require.NoError(state.PutSubnetOnlyValidator(sov)) initialSOVs[sov.ValidationID] = sov subnetIDs.Add(sov.SubnetID) @@ -1687,6 +1670,9 @@ func TestSubnetOnlyValidators(t *testing.T) { expectedSOVs := maps.Clone(initialSOVs) for _, sov := range test.sovs { + sov.RemainingBalanceOwner = []byte{} + sov.DeactivationOwner = []byte{} + require.NoError(d.PutSubnetOnlyValidator(sov)) expectedSOVs[sov.ValidationID] = sov subnetIDs.Add(sov.SubnetID) diff --git a/vms/platformvm/state/subnet_only_validator_test.go b/vms/platformvm/state/subnet_only_validator_test.go index 3ebf754382d9..c7194c7932cf 100644 --- a/vms/platformvm/state/subnet_only_validator_test.go +++ b/vms/platformvm/state/subnet_only_validator_test.go @@ -209,13 +209,22 @@ func TestSubnetOnlyValidator_DatabaseHelpers(t *testing.T) { pk := bls.PublicFromSecretKey(sk) pkBytes := bls.PublicKeyToUncompressedBytes(pk) - var owner fx.Owner = &secp256k1fx.OutputOwners{ + var remainingBalanceOwner fx.Owner = &secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{ ids.GenerateTestShortID(), }, } - ownerBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &owner) + remainingBalanceOwnerBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &remainingBalanceOwner) + require.NoError(err) + + var deactivationOwner fx.Owner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + } + deactivationOwnerBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &deactivationOwner) require.NoError(err) vdr := SubnetOnlyValidator{ @@ -223,7 +232,8 @@ func TestSubnetOnlyValidator_DatabaseHelpers(t *testing.T) { SubnetID: ids.GenerateTestID(), NodeID: ids.GenerateTestNodeID(), PublicKey: pkBytes, - RemainingBalanceOwner: ownerBytes, + RemainingBalanceOwner: remainingBalanceOwnerBytes, + DeactivationOwner: deactivationOwnerBytes, StartTime: rand.Uint64(), // #nosec G404 Weight: rand.Uint64(), // #nosec G404 MinNonce: rand.Uint64(), // #nosec G404 From d49f77b09b82be8fe8902620187ce078ec3be072 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 1 Oct 2024 16:35:06 -0400 Subject: [PATCH 107/199] Update convertSubnetTx --- vms/platformvm/txs/convert_subnet_tx.go | 20 ++++++-- vms/platformvm/txs/convert_subnet_tx_test.go | 48 +++++++++++++++---- .../txs/executor/standard_tx_executor.go | 11 +++-- .../txs/executor/standard_tx_executor_test.go | 4 +- vms/platformvm/txs/fee/calculator_test.go | 12 ----- vms/platformvm/txs/fee/complexity.go | 13 +++-- vms/platformvm/txs/fee/complexity_test.go | 45 +++++++++++------ wallet/chain/p/builder_test.go | 14 ++++-- 8 files changed, 118 insertions(+), 49 deletions(-) diff --git a/vms/platformvm/txs/convert_subnet_tx.go b/vms/platformvm/txs/convert_subnet_tx.go index 46a363f5bc16..b3dc6abb6175 100644 --- a/vms/platformvm/txs/convert_subnet_tx.go +++ b/vms/platformvm/txs/convert_subnet_tx.go @@ -11,8 +11,9 @@ import ( "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/vms/components/verify" - "github.com/ava-labs/avalanchego/vms/platformvm/fx" "github.com/ava-labs/avalanchego/vms/platformvm/signer" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/vms/types" ) @@ -96,7 +97,9 @@ type ConvertSubnetValidator struct { Signer signer.Signer `serialize:"true" json:"signer"` // Leftover $AVAX from the [Balance] will be issued to this owner once it is // removed from the validator set. - RemainingBalanceOwner fx.Owner `serialize:"true" json:"remainingBalanceOwner"` + RemainingBalanceOwner message.PChainOwner `serialize:"true" json:"remainingBalanceOwner"` + // This owner has the authority to manually deactivate this validator. + DeactivationOwner message.PChainOwner `serialize:"true" json:"deactivationOwner"` } func (v ConvertSubnetValidator) Compare(o ConvertSubnetValidator) int { @@ -107,7 +110,18 @@ func (v *ConvertSubnetValidator) Verify() error { if v.Weight == 0 { return ErrZeroWeight } - if err := verify.All(v.Signer, v.RemainingBalanceOwner); err != nil { + err := verify.All( + v.Signer, + &secp256k1fx.OutputOwners{ + Threshold: v.RemainingBalanceOwner.Threshold, + Addrs: v.RemainingBalanceOwner.Addresses, + }, + &secp256k1fx.OutputOwners{ + Threshold: v.DeactivationOwner.Threshold, + Addrs: v.DeactivationOwner.Addresses, + }, + ) + if err != nil { return err } if v.Signer.Key() == nil { diff --git a/vms/platformvm/txs/convert_subnet_tx_test.go b/vms/platformvm/txs/convert_subnet_tx_test.go index 3d122d5c9cf4..c1b212702914 100644 --- a/vms/platformvm/txs/convert_subnet_tx_test.go +++ b/vms/platformvm/txs/convert_subnet_tx_test.go @@ -21,6 +21,7 @@ import ( "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/platformvm/stakeable" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/vms/types" ) @@ -299,10 +300,15 @@ func TestConvertSubnetTxSerialization(t *testing.T) { Weight: 0x0102030405060708, Balance: units.Avax, Signer: signer.NewProofOfPossession(sk), - RemainingBalanceOwner: &secp256k1fx.OutputOwners{ - Locktime: 0, + RemainingBalanceOwner: message.PChainOwner{ Threshold: 1, - Addrs: []ids.ShortID{ + Addresses: []ids.ShortID{ + addr, + }, + }, + DeactivationOwner: message.PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ addr, }, }, @@ -557,7 +563,8 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { Weight: 1, Balance: 1, Signer: signer.NewProofOfPossession(sk), - RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{}, }, } validSubnetAuth = &secp256k1fx.Input{} @@ -648,7 +655,8 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { { Weight: 0, Signer: signer.NewProofOfPossession(sk), - RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{}, }, }, SubnetAuth: validSubnetAuth, @@ -664,7 +672,8 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { { Weight: 1, Signer: &signer.ProofOfPossession{}, - RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{}, }, }, SubnetAuth: validSubnetAuth, @@ -672,7 +681,7 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { expectedErr: bls.ErrFailedPublicKeyDecompress, }, { - name: "invalid validator owner", + name: "invalid validator remaining balance owner", tx: &ConvertSubnetTx{ BaseTx: validBaseTx, Subnet: validSubnetID, @@ -680,7 +689,27 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { { Weight: 1, Signer: signer.NewProofOfPossession(sk), - RemainingBalanceOwner: &secp256k1fx.OutputOwners{ + RemainingBalanceOwner: message.PChainOwner{ + Threshold: 1, + }, + DeactivationOwner: message.PChainOwner{}, + }, + }, + SubnetAuth: validSubnetAuth, + }, + expectedErr: secp256k1fx.ErrOutputUnspendable, + }, + { + name: "invalid validator deactivation owner", + tx: &ConvertSubnetTx{ + BaseTx: validBaseTx, + Subnet: validSubnetID, + Validators: []ConvertSubnetValidator{ + { + Weight: 1, + Signer: signer.NewProofOfPossession(sk), + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{ Threshold: 1, }, }, @@ -698,7 +727,8 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { { Weight: 1, Signer: &signer.Empty{}, - RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{}, }, }, SubnetAuth: validSubnetAuth, diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index f924ec6fee9c..908ecb77c1c3 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -534,17 +534,22 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { ) for i, vdr := range tx.Validators { vdr := vdr - balanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &vdr.RemainingBalanceOwner) + remainingBalanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &vdr.RemainingBalanceOwner) + if err != nil { + return err + } + deactivationOwner, err := txs.Codec.Marshal(txs.CodecVersion, &vdr.DeactivationOwner) if err != nil { return err } sov := state.SubnetOnlyValidator{ - ValidationID: txID.Prefix(uint64(i)), // TODO: The spec says this should be a postfix, not a preifx + ValidationID: tx.Subnet.Prefix(uint64(i)), // TODO: The spec says this should be a postfix, not a preifx SubnetID: tx.Subnet, NodeID: vdr.NodeID, PublicKey: bls.PublicKeyToUncompressedBytes(vdr.Signer.Key()), - RemainingBalanceOwner: balanceOwner, + RemainingBalanceOwner: remainingBalanceOwner, + DeactivationOwner: deactivationOwner, StartTime: startTime, Weight: vdr.Weight, MinNonce: 0, diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 47286a461e5e..a49f95371f50 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -41,6 +41,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/txs/txstest" "github.com/ava-labs/avalanchego/vms/platformvm/utxo" "github.com/ava-labs/avalanchego/vms/platformvm/utxo/utxomock" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" ) @@ -2571,7 +2572,8 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { Weight: 1, Balance: 1, Signer: signer.NewProofOfPossession(sk), - RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{}, }, }, test.builderOptions..., diff --git a/vms/platformvm/txs/fee/calculator_test.go b/vms/platformvm/txs/fee/calculator_test.go index d1674b77f7ed..843f20d13e9d 100644 --- a/vms/platformvm/txs/fee/calculator_test.go +++ b/vms/platformvm/txs/fee/calculator_test.go @@ -219,17 +219,5 @@ var ( }, expectedDynamicFee: 173_600, }, - { - name: "ConvertSubnetTx", - tx: "00000000002300003039000000000000000000000000000000000000000000000000000000000000000000000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db0000000700470de4a3e7309a000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c00000001000000000000000000000000000000000000000000000000000000000000000000000000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db0000000500470de4df820000000000010000000000000000a0673b4ee5ec44e57c8ab250dd7cd7b68d04421f64bd6559a4284a3ee358ff2b00000000000000000000000000000000000000000000000000000000000000000000000000000001e902a9a86640bfdb1cd0e36c0cc982b83e5765fa0000000000000001000000003b9aca000000001c8ef822e59dbe6330ab626cc459d302ada16ed1dc0a2abf7b98e8f297fdad93adc13467d9cd4c152b3448c1e036172142b20b33fd46c37cfaeacc45e87d88ab732e70cc8d123c7a21134a5b0846d888c53fec7ba6f56f82dad2cb04593be5d1610dc45220b09fa3932893081df15cbd1ce1d5410c48d3f6db135d6075007dc41dc88aefce6573353d1917c95b7cce46ac0000000b000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c0000000a0000000100000000000000020000000900000001376cfb1cb202f6c46ae19c7e05153ccd1fd37e8028ee55ff9a0d08deace609e42bf09f129e1049124446520fb2586fb8dce40de31cfc2c83947337210fe6b4be000000000900000001376cfb1cb202f6c46ae19c7e05153ccd1fd37e8028ee55ff9a0d08deace609e42bf09f129e1049124446520fb2586fb8dce40de31cfc2c83947337210fe6b4be00", - expectedStaticFeeErr: ErrUnsupportedTx, - expectedComplexity: gas.Dimensions{ - gas.Bandwidth: 680, // The length of the tx in bytes - gas.DBRead: IntrinsicConvertSubnetTxComplexities[gas.DBRead] + intrinsicInputDBRead, - gas.DBWrite: IntrinsicConvertSubnetTxComplexities[gas.DBWrite] + intrinsicInputDBWrite + intrinsicOutputDBWrite + intrinsicConvertSubnetValidatorDBWrite, - gas.Compute: 0, // TODO: implement - }, - expectedDynamicFee: 368_000, - }, } ) diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 50c5f377e34f..bcdc66bddf3e 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -66,7 +66,10 @@ const ( wrappers.LongLen + // weight wrappers.LongLen + // balance wrappers.IntLen + // signer typeID - wrappers.IntLen // owner typeID + wrappers.IntLen + // remaining balance owner threshold + wrappers.IntLen + // remaining balance owner num addresses + wrappers.IntLen + // deactivation owner threshold + wrappers.IntLen // deactivation owner num addresses intrinsicPoPBandwidth = bls.PublicKeyLen + // public key bls.SignatureLen // signature @@ -343,13 +346,17 @@ func convertSubnetValidatorComplexity(sov txs.ConvertSubnetValidator) (gas.Dimen if err != nil { return gas.Dimensions{}, err } - ownerComplexity, err := OwnerComplexity(sov.RemainingBalanceOwner) + + numAddresses := uint64(len(sov.RemainingBalanceOwner.Addresses) + len(sov.DeactivationOwner.Addresses)) + addressBandwidth, err := math.Mul(numAddresses, ids.ShortIDLen) if err != nil { return gas.Dimensions{}, err } return complexity.Add( &signerComplexity, - &ownerComplexity, + &gas.Dimensions{ + gas.Bandwidth: addressBandwidth, + }, ) } diff --git a/vms/platformvm/txs/fee/complexity_test.go b/vms/platformvm/txs/fee/complexity_test.go index bf51693987ca..f7305dfb8d51 100644 --- a/vms/platformvm/txs/fee/complexity_test.go +++ b/vms/platformvm/txs/fee/complexity_test.go @@ -20,6 +20,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/platformvm/stakeable" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) @@ -378,10 +379,11 @@ func TestConvertSubnetValidatorComplexity(t *testing.T) { name: "any can spend", vdr: txs.ConvertSubnetValidator{ Signer: &signer.ProofOfPossession{}, - RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{}, }, expected: gas.Dimensions{ - gas.Bandwidth: 204, + gas.Bandwidth: 200, gas.DBRead: 0, gas.DBWrite: 4, gas.Compute: 0, // TODO: implement @@ -389,18 +391,19 @@ func TestConvertSubnetValidatorComplexity(t *testing.T) { expectedErr: nil, }, { - name: "single owner", + name: "single remaining balance owner", vdr: txs.ConvertSubnetValidator{ Signer: &signer.ProofOfPossession{}, - RemainingBalanceOwner: &secp256k1fx.OutputOwners{ + RemainingBalanceOwner: message.PChainOwner{ Threshold: 1, - Addrs: []ids.ShortID{ + Addresses: []ids.ShortID{ ids.GenerateTestShortID(), }, }, + DeactivationOwner: message.PChainOwner{}, }, expected: gas.Dimensions{ - gas.Bandwidth: 224, + gas.Bandwidth: 220, gas.DBRead: 0, gas.DBWrite: 4, gas.Compute: 0, // TODO: implement @@ -408,22 +411,34 @@ func TestConvertSubnetValidatorComplexity(t *testing.T) { expectedErr: nil, }, { - name: "invalid signer", + name: "single deactivation owner", vdr: txs.ConvertSubnetValidator{ - Signer: nil, - RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + Signer: &signer.ProofOfPossession{}, + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, }, - expected: gas.Dimensions{}, - expectedErr: errUnsupportedSigner, + expected: gas.Dimensions{ + gas.Bandwidth: 220, + gas.DBRead: 0, + gas.DBWrite: 4, + gas.Compute: 0, // TODO: implement + }, + expectedErr: nil, }, { - name: "invalid owner", + name: "invalid signer", vdr: txs.ConvertSubnetValidator{ - Signer: &signer.ProofOfPossession{}, - RemainingBalanceOwner: nil, + Signer: nil, + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{}, }, expected: gas.Dimensions{}, - expectedErr: errUnsupportedOwner, + expectedErr: errUnsupportedSigner, }, } for _, test := range tests { diff --git a/wallet/chain/p/builder_test.go b/wallet/chain/p/builder_test.go index a42725aec4f7..3b61524f9b59 100644 --- a/wallet/chain/p/builder_test.go +++ b/wallet/chain/p/builder_test.go @@ -25,6 +25,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/stakeable" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/vms/types" "github.com/ava-labs/avalanchego/wallet/chain/p/builder" @@ -682,9 +683,15 @@ func TestConvertSubnetTx(t *testing.T) { Weight: rand.Uint64(), //#nosec G404 Balance: units.Avax, Signer: signer.NewProofOfPossession(sk0), - RemainingBalanceOwner: &secp256k1fx.OutputOwners{ + RemainingBalanceOwner: message.PChainOwner{ Threshold: 1, - Addrs: []ids.ShortID{ + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + DeactivationOwner: message.PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ ids.GenerateTestShortID(), }, }, @@ -694,7 +701,8 @@ func TestConvertSubnetTx(t *testing.T) { Weight: rand.Uint64(), //#nosec G404 Balance: 2 * units.Avax, Signer: signer.NewProofOfPossession(sk1), - RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{}, }, } ) From 92f2af94c5401de66e4eaf8de3957ed7316d88c9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 1 Oct 2024 16:49:50 -0400 Subject: [PATCH 108/199] Update registerSubnetValdiatorTx --- .../txs/executor/standard_tx_executor.go | 10 ++++++++-- vms/platformvm/txs/fee/complexity.go | 6 ------ .../txs/register_subnet_validator_tx.go | 7 ------- wallet/chain/p/builder/builder.go | 17 +++-------------- wallet/chain/p/builder/builder_with_options.go | 2 -- 5 files changed, 11 insertions(+), 31 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 584fff7ebb23..ef3574a99e20 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -725,7 +725,12 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return err } - balanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &tx.RemainingBalanceOwner) + remainingBalanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &msg.RemainingBalanceOwner) + if err != nil { + return err + } + + deactivationOwner, err := txs.Codec.Marshal(txs.CodecVersion, &msg.DisableOwner) if err != nil { return err } @@ -735,7 +740,8 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal SubnetID: msg.SubnetID, NodeID: nodeID, PublicKey: bls.PublicKeyToUncompressedBytes(pop.Key()), - RemainingBalanceOwner: balanceOwner, + RemainingBalanceOwner: remainingBalanceOwner, + DeactivationOwner: deactivationOwner, StartTime: currentTimestampUnix, Weight: msg.Weight, MinNonce: 0, diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index e37efb6a48a7..2316cdd7c482 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -201,7 +201,6 @@ var ( gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + wrappers.LongLen + // balance wrappers.IntLen + // signer typeID - wrappers.IntLen + // owner typeID wrappers.IntLen, // message length gas.DBRead: 0, // TODO gas.DBWrite: 0, // TODO @@ -707,17 +706,12 @@ func (c *complexityVisitor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVali if err != nil { return err } - ownerComplexity, err := OwnerComplexity(tx.RemainingBalanceOwner) - if err != nil { - return err - } warpComplexity, err := WarpComplexity(tx.Message) if err != nil { return err } c.output, err = IntrinsicConvertSubnetTxComplexities.Add( &baseTxComplexity, - &ownerComplexity, &warpComplexity, ) return err diff --git a/vms/platformvm/txs/register_subnet_validator_tx.go b/vms/platformvm/txs/register_subnet_validator_tx.go index bb1d3e2d4ecc..a5a7a0192329 100644 --- a/vms/platformvm/txs/register_subnet_validator_tx.go +++ b/vms/platformvm/txs/register_subnet_validator_tx.go @@ -6,7 +6,6 @@ package txs import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/vms/platformvm/fx" "github.com/ava-labs/avalanchego/vms/types" ) @@ -19,9 +18,6 @@ type RegisterSubnetValidatorTx struct { Balance uint64 `serialize:"true" json:"balance"` // ProofOfPossession of the BLS key that is included in the Message. ProofOfPossession [bls.SignatureLen]byte `serialize:"true" json:"proofOfPossession"` - // Leftover $AVAX from the Subnet Validator's Balance will be issued to - // this owner after it is removed from the validator set. - RemainingBalanceOwner fx.Owner `serialize:"true" json:"remainingBalanceOwner"` // Message is expected to be a signed Warp message containing an // AddressedCall payload with the RegisterSubnetValidator message. Message types.JSONByteSlice `serialize:"true" json:"message"` @@ -39,9 +35,6 @@ func (tx *RegisterSubnetValidatorTx) SyntacticVerify(ctx *snow.Context) error { if err := tx.BaseTx.SyntacticVerify(ctx); err != nil { return err } - if err := tx.RemainingBalanceOwner.Verify(); err != nil { - return err - } tx.SyntacticallyVerified = true return nil diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index b15a567d3690..ed5cf7d11431 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -170,14 +170,11 @@ type Builder interface { // - [balance] that the validator should allocate to continuous fees // - [proofOfPossession] is the BLS PoP for the key included in the Warp // message - // - [remainingBalanceOwner] specifies the owner to send any of the - // remaining balance after removing the continuous fee // - [message] is the Warp message that authorizes this validator to be // added NewRegisterSubnetValidatorTx( balance uint64, proofOfPossession [bls.SignatureLen]byte, - remainingBalanceOwner *secp256k1fx.OutputOwners, message []byte, options ...common.Option, ) (*txs.RegisterSubnetValidatorTx, error) @@ -882,7 +879,6 @@ func (b *builder) NewConvertSubnetTx( func (b *builder) NewRegisterSubnetValidatorTx( balance uint64, proofOfPossession [bls.SignatureLen]byte, - remainingBalanceOwner *secp256k1fx.OutputOwners, message []byte, options ...common.Option, ) (*txs.RegisterSubnetValidatorTx, error) { @@ -898,17 +894,12 @@ func (b *builder) NewRegisterSubnetValidatorTx( memoComplexity := gas.Dimensions{ gas.Bandwidth: uint64(len(memo)), } - ownerComplexity, err := fee.OwnerComplexity(remainingBalanceOwner) - if err != nil { - return nil, err - } warpComplexity, err := fee.WarpComplexity(message) if err != nil { return nil, err } complexity, err := fee.IntrinsicRegisterSubnetValidatorTxComplexities.Add( &memoComplexity, - &ownerComplexity, &warpComplexity, ) if err != nil { @@ -927,7 +918,6 @@ func (b *builder) NewRegisterSubnetValidatorTx( return nil, err } - utils.Sort(remainingBalanceOwner.Addrs) tx := &txs.RegisterSubnetValidatorTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.context.NetworkID, @@ -936,10 +926,9 @@ func (b *builder) NewRegisterSubnetValidatorTx( Outs: outputs, Memo: memo, }}, - Balance: balance, - ProofOfPossession: proofOfPossession, - RemainingBalanceOwner: remainingBalanceOwner, - Message: message, + Balance: balance, + ProofOfPossession: proofOfPossession, + Message: message, } return tx, b.initCtx(tx) } diff --git a/wallet/chain/p/builder/builder_with_options.go b/wallet/chain/p/builder/builder_with_options.go index fd83ce62f0ab..2147514fd778 100644 --- a/wallet/chain/p/builder/builder_with_options.go +++ b/wallet/chain/p/builder/builder_with_options.go @@ -175,14 +175,12 @@ func (b *builderWithOptions) NewConvertSubnetTx( func (b *builderWithOptions) NewRegisterSubnetValidatorTx( balance uint64, proofOfPossession [bls.SignatureLen]byte, - remainingBalanceOwner *secp256k1fx.OutputOwners, message []byte, options ...common.Option, ) (*txs.RegisterSubnetValidatorTx, error) { return b.builder.NewRegisterSubnetValidatorTx( balance, proofOfPossession, - remainingBalanceOwner, message, common.UnionOptions(b.options, options)..., ) From 6e06c0c22a3919cb89e1adf9d321ea3b342f9445 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 1 Oct 2024 16:59:29 -0400 Subject: [PATCH 109/199] Update setSubnetValidatorWeightTx --- .../txs/executor/standard_tx_executor.go | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 790d4164ee19..9b3a0a8c60e3 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -21,7 +21,6 @@ import ( "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/components/verify" - "github.com/ava-labs/avalanchego/vms/platformvm/fx" "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" @@ -29,6 +28,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" safemath "github.com/ava-labs/avalanchego/utils/math" ) @@ -857,7 +857,7 @@ func (e *StandardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat if msg.Weight == 0 && sov.EndAccumulatedFee != 0 { // If we are removing an active validator, we need to refund the // remaining balance. - var remainingBalanceOwner fx.Owner + var remainingBalanceOwner message.PChainOwner if _, err := txs.Codec.Unmarshal(sov.RemainingBalanceOwner, &remainingBalanceOwner); err != nil { return err } @@ -870,16 +870,6 @@ func (e *StandardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat } remainingBalance := sov.EndAccumulatedFee - accruedFees - outIntf, err := e.Fx.CreateOutput(remainingBalance, remainingBalanceOwner) - if err != nil { - return fmt.Errorf("failed to create output: %w", err) - } - - out, ok := outIntf.(verify.State) - if !ok { - return ErrInvalidState - } - utxo := &avax.UTXO{ UTXOID: avax.UTXOID{ TxID: txID, @@ -888,7 +878,13 @@ func (e *StandardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat Asset: avax.Asset{ ID: e.Ctx.AVAXAssetID, }, - Out: out, + Out: &secp256k1fx.TransferOutput{ + Amt: remainingBalance, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: remainingBalanceOwner.Threshold, + Addrs: remainingBalanceOwner.Addresses, + }, + }, } e.State.AddUTXO(utxo) } From b361effbbedd31f3ba41403c7b2d8d511d205324 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 12:42:46 -0400 Subject: [PATCH 110/199] ACP-77: Implement DisableSubnetValidatorTx --- vms/platformvm/metrics/tx_metrics.go | 7 ++ .../txs/disable_subnet_validator_tx.go | 45 +++++++ .../txs/executor/atomic_tx_executor.go | 4 + .../txs/executor/proposal_tx_executor.go | 4 + .../txs/executor/staker_tx_verification.go | 6 +- .../txs/executor/standard_tx_executor.go | 113 +++++++++++++++++- .../txs/executor/subnet_tx_verification.go | 42 ++++--- vms/platformvm/txs/executor/warp_verifier.go | 6 +- vms/platformvm/txs/fee/complexity.go | 24 ++++ vms/platformvm/txs/fee/static_calculator.go | 4 + vms/platformvm/txs/visitor.go | 1 + wallet/chain/p/builder/builder.go | 22 ++-- wallet/chain/p/signer/signer.go | 2 +- wallet/chain/p/signer/visitor.go | 43 ++++--- wallet/chain/p/wallet/backend.go | 28 ++--- wallet/chain/p/wallet/backend_visitor.go | 32 ++++- 16 files changed, 319 insertions(+), 64 deletions(-) create mode 100644 vms/platformvm/txs/disable_subnet_validator_tx.go diff --git a/vms/platformvm/metrics/tx_metrics.go b/vms/platformvm/metrics/tx_metrics.go index 5077b63c15df..b411e226c565 100644 --- a/vms/platformvm/metrics/tx_metrics.go +++ b/vms/platformvm/metrics/tx_metrics.go @@ -166,3 +166,10 @@ func (m *txMetrics) IncreaseBalanceTx(*txs.IncreaseBalanceTx) error { }).Inc() return nil } + +func (m *txMetrics) DisableSubnetValidatorTx(*txs.DisableSubnetValidatorTx) error { + m.numTxs.With(prometheus.Labels{ + txLabel: "disable_subnet_validator", + }).Inc() + return nil +} diff --git a/vms/platformvm/txs/disable_subnet_validator_tx.go b/vms/platformvm/txs/disable_subnet_validator_tx.go new file mode 100644 index 000000000000..ca03f4208644 --- /dev/null +++ b/vms/platformvm/txs/disable_subnet_validator_tx.go @@ -0,0 +1,45 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txs + +import ( + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/vms/components/verify" +) + +var _ UnsignedTx = (*DisableSubnetValidatorTx)(nil) + +type DisableSubnetValidatorTx struct { + // Metadata, inputs and outputs + BaseTx `serialize:"true"` + // ID corresponding to the validator + ValidationID ids.ID `json:"validationID"` + // Authorizes this validator to be disabled + DisableAuth verify.Verifiable `serialize:"true" json:"disableAuthorization"` +} + +func (tx *DisableSubnetValidatorTx) SyntacticVerify(ctx *snow.Context) error { + switch { + case tx == nil: + return ErrNilTx + case tx.SyntacticallyVerified: + // already passed syntactic verification + return nil + } + + if err := tx.BaseTx.SyntacticVerify(ctx); err != nil { + return err + } + if err := tx.DisableAuth.Verify(); err != nil { + return err + } + + tx.SyntacticallyVerified = true + return nil +} + +func (tx *DisableSubnetValidatorTx) Visit(visitor Visitor) error { + return visitor.DisableSubnetValidatorTx(tx) +} diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index 8cbe7b123d04..7bf4566bce6a 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -98,6 +98,10 @@ func (*AtomicTxExecutor) IncreaseBalanceTx(*txs.IncreaseBalanceTx) error { return ErrWrongTxType } +func (*AtomicTxExecutor) DisableSubnetValidatorTx(*txs.DisableSubnetValidatorTx) error { + return ErrWrongTxType +} + func (e *AtomicTxExecutor) ImportTx(tx *txs.ImportTx) error { return e.atomicTx(tx) } diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go index a8a2b443d980..68906d920242 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor.go @@ -115,6 +115,10 @@ func (*ProposalTxExecutor) IncreaseBalanceTx(*txs.IncreaseBalanceTx) error { return ErrWrongTxType } +func (*ProposalTxExecutor) DisableSubnetValidatorTx(*txs.DisableSubnetValidatorTx) error { + return ErrWrongTxType +} + func (e *ProposalTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { // AddValidatorTx is a proposal transaction until the Banff fork // activation. Following the activation, AddValidatorTxs must be issued into diff --git a/vms/platformvm/txs/executor/staker_tx_verification.go b/vms/platformvm/txs/executor/staker_tx_verification.go index d458c01ab259..7b05883ed534 100644 --- a/vms/platformvm/txs/executor/staker_tx_verification.go +++ b/vms/platformvm/txs/executor/staker_tx_verification.go @@ -253,7 +253,7 @@ func verifyAddSubnetValidatorTx( return err } - baseTxCreds, err := verifyPoASubnetAuthorization(backend, chainState, sTx, tx.SubnetValidator.Subnet, tx.SubnetAuth) + baseTxCreds, err := verifyPoASubnetAuthorization(backend.Fx, chainState, sTx, tx.SubnetValidator.Subnet, tx.SubnetAuth) if err != nil { return err } @@ -343,7 +343,7 @@ func verifyRemoveSubnetValidatorTx( return vdr, isCurrentValidator, nil } - baseTxCreds, err := verifySubnetAuthorization(backend, chainState, sTx, tx.Subnet, tx.SubnetAuth) + baseTxCreds, err := verifySubnetAuthorization(backend.Fx, chainState, sTx, tx.Subnet, tx.SubnetAuth) if err != nil { return nil, false, err } @@ -792,7 +792,7 @@ func verifyTransferSubnetOwnershipTx( return nil } - baseTxCreds, err := verifySubnetAuthorization(backend, chainState, sTx, tx.Subnet, tx.SubnetAuth) + baseTxCreds, err := verifySubnetAuthorization(backend.Fx, chainState, sTx, tx.Subnet, tx.SubnetAuth) if err != nil { return err } diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 2b81e4354542..ba9996d92de3 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -86,7 +86,7 @@ func (e *StandardTxExecutor) CreateChainTx(tx *txs.CreateChainTx) error { return err } - baseTxCreds, err := verifyPoASubnetAuthorization(e.Backend, e.State, e.Tx, tx.SubnetID, tx.SubnetAuth) + baseTxCreds, err := verifyPoASubnetAuthorization(e.Fx, e.State, e.Tx, tx.SubnetID, tx.SubnetAuth) if err != nil { return err } @@ -475,7 +475,7 @@ func (e *StandardTxExecutor) TransformSubnetTx(tx *txs.TransformSubnetTx) error return errMaxStakeDurationTooLarge } - baseTxCreds, err := verifyPoASubnetAuthorization(e.Backend, e.State, e.Tx, tx.Subnet, tx.SubnetAuth) + baseTxCreds, err := verifyPoASubnetAuthorization(e.Fx, e.State, e.Tx, tx.Subnet, tx.SubnetAuth) if err != nil { return err } @@ -532,7 +532,7 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { return err } - baseTxCreds, err := verifyPoASubnetAuthorization(e.Backend, e.State, e.Tx, tx.Subnet, tx.SubnetAuth) + baseTxCreds, err := verifyPoASubnetAuthorization(e.Fx, e.State, e.Tx, tx.Subnet, tx.SubnetAuth) if err != nil { return err } @@ -961,6 +961,113 @@ func (e *StandardTxExecutor) IncreaseBalanceTx(tx *txs.IncreaseBalanceTx) error return nil } +func (e *StandardTxExecutor) DisableSubnetValidatorTx(tx *txs.DisableSubnetValidatorTx) error { + var ( + currentTimestamp = e.State.GetTimestamp() + upgrades = e.Backend.Config.UpgradeConfig + ) + if !upgrades.IsEtnaActivated(currentTimestamp) { + return errEtnaUpgradeNotActive + } + + if err := e.Tx.SyntacticVerify(e.Ctx); err != nil { + return err + } + + if err := avax.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil { + return err + } + + sov, err := e.State.GetSubnetOnlyValidator(tx.ValidationID) + if err != nil { + return err + } + + var disableOwner message.PChainOwner + if _, err := txs.Codec.Unmarshal(sov.DeactivationOwner, &disableOwner); err != nil { + return err + } + + baseTxCreds, err := verifyAuthorization( + e.Fx, + e.Tx, + &secp256k1fx.OutputOwners{ + Threshold: disableOwner.Threshold, + Addrs: disableOwner.Addresses, + }, + tx.DisableAuth, + ) + if err != nil { + return err + } + + // Verify the flowcheck + fee, err := e.FeeCalculator.CalculateFee(tx) + if err != nil { + return err + } + + if err := e.Backend.FlowChecker.VerifySpend( + tx, + e.State, + tx.Ins, + tx.Outs, + baseTxCreds, + map[ids.ID]uint64{ + e.Ctx.AVAXAssetID: fee, + }, + ); err != nil { + return err + } + + txID := e.Tx.ID() + + // Consume the UTXOS + avax.Consume(e.State, tx.Ins) + // Produce the UTXOS + avax.Produce(e.State, txID, tx.Outs) + + // If the validator is already disabled, there is nothing to do. + if sov.EndAccumulatedFee == 0 { + return nil + } + + var remainingBalanceOwner message.PChainOwner + if _, err := txs.Codec.Unmarshal(sov.RemainingBalanceOwner, &remainingBalanceOwner); err != nil { + return err + } + + accruedFees := e.State.GetAccruedFees() + if sov.EndAccumulatedFee <= accruedFees { + // This should never happen as the validator should have been + // evicted. + return fmt.Errorf("validator has insufficient funds to cover accrued fees") + } + remainingBalance := sov.EndAccumulatedFee - accruedFees + + utxo := &avax.UTXO{ + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: uint32(len(tx.Outs)), + }, + Asset: avax.Asset{ + ID: e.Ctx.AVAXAssetID, + }, + Out: &secp256k1fx.TransferOutput{ + Amt: remainingBalance, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: remainingBalanceOwner.Threshold, + Addrs: remainingBalanceOwner.Addresses, + }, + }, + } + e.State.AddUTXO(utxo) + + // Disable the validator + sov.EndAccumulatedFee = 0 + return e.State.PutSubnetOnlyValidator(sov) +} + func (e *StandardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { if err := verifyAddPermissionlessValidatorTx( e.Backend, diff --git a/vms/platformvm/txs/executor/subnet_tx_verification.go b/vms/platformvm/txs/executor/subnet_tx_verification.go index 6e5ecb9a34f5..ac134eb8cbd1 100644 --- a/vms/platformvm/txs/executor/subnet_tx_verification.go +++ b/vms/platformvm/txs/executor/subnet_tx_verification.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/ava-labs/avalanchego/vms/platformvm/fx" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" ) @@ -24,13 +25,13 @@ var ( // subnet. This is an extension of [verifySubnetAuthorization] that additionally // verifies that the subnet being modified is currently a PoA subnet. func verifyPoASubnetAuthorization( - backend *Backend, + fx fx.Fx, chainState state.Chain, sTx *txs.Tx, subnetID ids.ID, subnetAuth verify.Verifiable, ) ([]verify.Verifiable, error) { - creds, err := verifySubnetAuthorization(backend, chainState, sTx, subnetID, subnetAuth) + creds, err := verifySubnetAuthorization(fx, chainState, sTx, subnetID, subnetAuth) if err != nil { return nil, err } @@ -59,28 +60,41 @@ func verifyPoASubnetAuthorization( // Returns the remaining tx credentials that should be used to authorize the // other operations in the tx. func verifySubnetAuthorization( - backend *Backend, + fx fx.Fx, chainState state.Chain, - sTx *txs.Tx, + tx *txs.Tx, subnetID ids.ID, subnetAuth verify.Verifiable, ) ([]verify.Verifiable, error) { - if len(sTx.Creds) == 0 { - // Ensure there is at least one credential for the subnet authorization - return nil, errWrongNumberOfCredentials - } - - baseTxCredsLen := len(sTx.Creds) - 1 - subnetCred := sTx.Creds[baseTxCredsLen] - subnetOwner, err := chainState.GetSubnetOwner(subnetID) if err != nil { return nil, err } - if err := backend.Fx.VerifyPermission(sTx.Unsigned, subnetAuth, subnetCred, subnetOwner); err != nil { + return verifyAuthorization(fx, tx, subnetOwner, subnetAuth) +} + +// verifyAuthorization carries out the validation of an auth. The last +// credential in [sTx.Creds] is used as the authorization. +// Returns the remaining tx credentials that should be used to authorize the +// other operations in the tx. +func verifyAuthorization( + fx fx.Fx, + tx *txs.Tx, + owner fx.Owner, + auth verify.Verifiable, +) ([]verify.Verifiable, error) { + if len(tx.Creds) == 0 { + // Ensure there is at least one credential for the subnet authorization + return nil, errWrongNumberOfCredentials + } + + baseTxCredsLen := len(tx.Creds) - 1 + authCred := tx.Creds[baseTxCredsLen] + + if err := fx.VerifyPermission(tx.Unsigned, auth, authCred, owner); err != nil { return nil, fmt.Errorf("%w: %w", errUnauthorizedSubnetModification, err) } - return sTx.Creds[:baseTxCredsLen], nil + return tx.Creds[:baseTxCredsLen], nil } diff --git a/vms/platformvm/txs/executor/warp_verifier.go b/vms/platformvm/txs/executor/warp_verifier.go index edf05aa61f05..dc99d7c377a6 100644 --- a/vms/platformvm/txs/executor/warp_verifier.go +++ b/vms/platformvm/txs/executor/warp_verifier.go @@ -106,7 +106,11 @@ func (*warpVerifier) ConvertSubnetTx(*txs.ConvertSubnetTx) error { return nil } -func (w *warpVerifier) IncreaseBalanceTx(tx *txs.IncreaseBalanceTx) error { +func (w *warpVerifier) IncreaseBalanceTx(*txs.IncreaseBalanceTx) error { + return nil +} + +func (w *warpVerifier) DisableSubnetValidatorTx(*txs.DisableSubnetValidatorTx) error { return nil } diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 48104fd84a3f..3728dbdcbd32 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -221,6 +221,14 @@ var ( gas.DBWrite: 0, // TODO gas.Compute: 0, } + IntrinsicDisableSubnetValidatorTxComplexities = gas.Dimensions{ + gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + + ids.IDLen + // validationID + wrappers.IntLen, // auth typeID + gas.DBRead: 0, // TODO + gas.DBWrite: 0, // TODO + gas.Compute: 0, + } errUnsupportedOutput = errors.New("unsupported output type") errUnsupportedInput = errors.New("unsupported input type") @@ -759,6 +767,22 @@ func (c *complexityVisitor) IncreaseBalanceTx(tx *txs.IncreaseBalanceTx) error { return err } +func (c *complexityVisitor) DisableSubnetValidatorTx(tx *txs.DisableSubnetValidatorTx) error { + baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) + if err != nil { + return err + } + authComplexity, err := AuthComplexity(tx.DisableAuth) + if err != nil { + return err + } + c.output, err = IntrinsicDisableSubnetValidatorTxComplexities.Add( + &baseTxComplexity, + &authComplexity, + ) + return err +} + func baseTxComplexity(tx *txs.BaseTx) (gas.Dimensions, error) { outputsComplexity, err := OutputComplexity(tx.Outs...) if err != nil { diff --git a/vms/platformvm/txs/fee/static_calculator.go b/vms/platformvm/txs/fee/static_calculator.go index f6b9c84daf55..354c70fa8050 100644 --- a/vms/platformvm/txs/fee/static_calculator.go +++ b/vms/platformvm/txs/fee/static_calculator.go @@ -63,6 +63,10 @@ func (*staticVisitor) IncreaseBalanceTx(*txs.IncreaseBalanceTx) error { return ErrUnsupportedTx } +func (*staticVisitor) DisableSubnetValidatorTx(*txs.DisableSubnetValidatorTx) error { + return ErrUnsupportedTx +} + func (c *staticVisitor) AddValidatorTx(*txs.AddValidatorTx) error { c.fee = c.config.AddPrimaryNetworkValidatorFee return nil diff --git a/vms/platformvm/txs/visitor.go b/vms/platformvm/txs/visitor.go index fbaf2a12124d..dead3500dcb2 100644 --- a/vms/platformvm/txs/visitor.go +++ b/vms/platformvm/txs/visitor.go @@ -31,4 +31,5 @@ type Visitor interface { RegisterSubnetValidatorTx(*RegisterSubnetValidatorTx) error SetSubnetValidatorWeightTx(*SetSubnetValidatorWeightTx) error IncreaseBalanceTx(*IncreaseBalanceTx) error + DisableSubnetValidatorTx(*DisableSubnetValidatorTx) error } diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index 15952408dbd3..625af0e29e3c 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -313,7 +313,7 @@ type Builder interface { type Backend interface { UTXOs(ctx context.Context, sourceChainID ids.ID) ([]*avax.UTXO, error) - GetSubnetOwner(ctx context.Context, subnetID ids.ID) (fx.Owner, error) + GetOwner(ctx context.Context, subnetID ids.ID) (fx.Owner, error) } type builder struct { @@ -470,7 +470,7 @@ func (b *builder) NewAddSubnetValidatorTx( toStake := map[ids.ID]uint64{} ops := common.NewOptions(options) - subnetAuth, err := b.authorizeSubnet(vdr.Subnet, ops) + subnetAuth, err := b.authorize(vdr.Subnet, ops) if err != nil { return nil, err } @@ -528,7 +528,7 @@ func (b *builder) NewRemoveSubnetValidatorTx( toStake := map[ids.ID]uint64{} ops := common.NewOptions(options) - subnetAuth, err := b.authorizeSubnet(subnetID, ops) + subnetAuth, err := b.authorize(subnetID, ops) if err != nil { return nil, err } @@ -631,7 +631,7 @@ func (b *builder) NewCreateChainTx( toStake := map[ids.ID]uint64{} ops := common.NewOptions(options) - subnetAuth, err := b.authorizeSubnet(subnetID, ops) + subnetAuth, err := b.authorize(subnetID, ops) if err != nil { return nil, err } @@ -762,7 +762,7 @@ func (b *builder) NewTransferSubnetOwnershipTx( toStake := map[ids.ID]uint64{} ops := common.NewOptions(options) - subnetAuth, err := b.authorizeSubnet(subnetID, ops) + subnetAuth, err := b.authorize(subnetID, ops) if err != nil { return nil, err } @@ -837,7 +837,7 @@ func (b *builder) NewConvertSubnetTx( toStake := map[ids.ID]uint64{} ops := common.NewOptions(options) - subnetAuth, err := b.authorizeSubnet(subnetID, ops) + subnetAuth, err := b.authorize(subnetID, ops) if err != nil { return nil, err } @@ -1274,7 +1274,7 @@ func (b *builder) NewTransformSubnetTx( toStake := map[ids.ID]uint64{} ops := common.NewOptions(options) - subnetAuth, err := b.authorizeSubnet(subnetID, ops) + subnetAuth, err := b.authorize(subnetID, ops) if err != nil { return nil, err } @@ -1813,12 +1813,12 @@ func (b *builder) spend( return s.inputs, s.changeOutputs, s.stakeOutputs, nil } -func (b *builder) authorizeSubnet(subnetID ids.ID, options *common.Options) (*secp256k1fx.Input, error) { - ownerIntf, err := b.backend.GetSubnetOwner(options.Context(), subnetID) +func (b *builder) authorize(ownerID ids.ID, options *common.Options) (*secp256k1fx.Input, error) { + ownerIntf, err := b.backend.GetOwner(options.Context(), ownerID) if err != nil { return nil, fmt.Errorf( - "failed to fetch subnet owner for %q: %w", - subnetID, + "failed to fetch owner for %q: %w", + ownerID, err, ) } diff --git a/wallet/chain/p/signer/signer.go b/wallet/chain/p/signer/signer.go index 08b3a9d9963b..d0c8c0dd07fa 100644 --- a/wallet/chain/p/signer/signer.go +++ b/wallet/chain/p/signer/signer.go @@ -29,7 +29,7 @@ type Signer interface { type Backend interface { GetUTXO(ctx stdcontext.Context, chainID, utxoID ids.ID) (*avax.UTXO, error) - GetSubnetOwner(ctx stdcontext.Context, subnetID ids.ID) (fx.Owner, error) + GetOwner(ctx stdcontext.Context, ownerID ids.ID) (fx.Owner, error) } type txSigner struct { diff --git a/wallet/chain/p/signer/visitor.go b/wallet/chain/p/signer/visitor.go index a4fc2aa94e8a..53a2f2a3ddbe 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -28,7 +28,7 @@ var ( ErrUnknownInputType = errors.New("unknown input type") ErrUnknownOutputType = errors.New("unknown output type") ErrInvalidUTXOSigIndex = errors.New("invalid UTXO signature index") - ErrUnknownSubnetAuthType = errors.New("unknown subnet auth type") + ErrUnknownAuthType = errors.New("unknown auth type") ErrUnknownOwnerType = errors.New("unknown owner type") ErrUnknownCredentialType = errors.New("unknown credential type") @@ -72,7 +72,7 @@ func (s *visitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { if err != nil { return err } - subnetAuthSigners, err := s.getSubnetSigners(tx.SubnetValidator.Subnet, tx.SubnetAuth) + subnetAuthSigners, err := s.getAuthSigners(tx.SubnetValidator.Subnet, tx.SubnetAuth) if err != nil { return err } @@ -93,7 +93,7 @@ func (s *visitor) CreateChainTx(tx *txs.CreateChainTx) error { if err != nil { return err } - subnetAuthSigners, err := s.getSubnetSigners(tx.SubnetID, tx.SubnetAuth) + subnetAuthSigners, err := s.getAuthSigners(tx.SubnetID, tx.SubnetAuth) if err != nil { return err } @@ -135,7 +135,7 @@ func (s *visitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error if err != nil { return err } - subnetAuthSigners, err := s.getSubnetSigners(tx.Subnet, tx.SubnetAuth) + subnetAuthSigners, err := s.getAuthSigners(tx.Subnet, tx.SubnetAuth) if err != nil { return err } @@ -148,7 +148,7 @@ func (s *visitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) e if err != nil { return err } - subnetAuthSigners, err := s.getSubnetSigners(tx.Subnet, tx.SubnetAuth) + subnetAuthSigners, err := s.getAuthSigners(tx.Subnet, tx.SubnetAuth) if err != nil { return err } @@ -161,7 +161,7 @@ func (s *visitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { if err != nil { return err } - subnetAuthSigners, err := s.getSubnetSigners(tx.Subnet, tx.SubnetAuth) + subnetAuthSigners, err := s.getAuthSigners(tx.Subnet, tx.SubnetAuth) if err != nil { return err } @@ -193,12 +193,25 @@ func (s *visitor) IncreaseBalanceTx(tx *txs.IncreaseBalanceTx) error { return sign(s.tx, true, txSigners) } +func (s *visitor) DisableSubnetValidatorTx(tx *txs.DisableSubnetValidatorTx) error { + txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) + if err != nil { + return err + } + validatorAuthSigners, err := s.getAuthSigners(tx.ValidationID, tx.DisableAuth) + if err != nil { + return err + } + txSigners = append(txSigners, validatorAuthSigners) + return sign(s.tx, true, txSigners) +} + func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err } - subnetAuthSigners, err := s.getSubnetSigners(tx.Subnet, tx.SubnetAuth) + subnetAuthSigners, err := s.getAuthSigners(tx.Subnet, tx.SubnetAuth) if err != nil { return err } @@ -277,17 +290,17 @@ func (s *visitor) getSigners(sourceChainID ids.ID, ins []*avax.TransferableInput return txSigners, nil } -func (s *visitor) getSubnetSigners(subnetID ids.ID, subnetAuth verify.Verifiable) ([]keychain.Signer, error) { - subnetInput, ok := subnetAuth.(*secp256k1fx.Input) +func (s *visitor) getAuthSigners(ownerID ids.ID, auth verify.Verifiable) ([]keychain.Signer, error) { + input, ok := auth.(*secp256k1fx.Input) if !ok { - return nil, ErrUnknownSubnetAuthType + return nil, ErrUnknownAuthType } - ownerIntf, err := s.backend.GetSubnetOwner(s.ctx, subnetID) + ownerIntf, err := s.backend.GetOwner(s.ctx, ownerID) if err != nil { return nil, fmt.Errorf( - "failed to fetch subnet owner for %q: %w", - subnetID, + "failed to fetch owner for %q: %w", + ownerID, err, ) } @@ -296,8 +309,8 @@ func (s *visitor) getSubnetSigners(subnetID ids.ID, subnetAuth verify.Verifiable return nil, ErrUnknownOwnerType } - authSigners := make([]keychain.Signer, len(subnetInput.SigIndices)) - for sigIndex, addrIndex := range subnetInput.SigIndices { + authSigners := make([]keychain.Signer, len(input.SigIndices)) + for sigIndex, addrIndex := range input.SigIndices { if addrIndex >= uint32(len(owner.Addrs)) { return nil, ErrInvalidUTXOSigIndex } diff --git a/wallet/chain/p/wallet/backend.go b/wallet/chain/p/wallet/backend.go index f46902fa8a51..ddcf1c4f6de3 100644 --- a/wallet/chain/p/wallet/backend.go +++ b/wallet/chain/p/wallet/backend.go @@ -34,15 +34,15 @@ type backend struct { context *builder.Context - subnetOwnerLock sync.RWMutex - subnetOwner map[ids.ID]fx.Owner // subnetID -> owner + ownersLock sync.RWMutex + owners map[ids.ID]fx.Owner // subnetID -> owner } -func NewBackend(context *builder.Context, utxos common.ChainUTXOs, subnetOwner map[ids.ID]fx.Owner) Backend { +func NewBackend(context *builder.Context, utxos common.ChainUTXOs, owners map[ids.ID]fx.Owner) Backend { return &backend{ - ChainUTXOs: utxos, - context: context, - subnetOwner: subnetOwner, + ChainUTXOs: utxos, + context: context, + owners: owners, } } @@ -79,20 +79,20 @@ func (b *backend) removeUTXOs(ctx context.Context, sourceChain ids.ID, utxoIDs s return nil } -func (b *backend) GetSubnetOwner(_ context.Context, subnetID ids.ID) (fx.Owner, error) { - b.subnetOwnerLock.RLock() - defer b.subnetOwnerLock.RUnlock() +func (b *backend) GetOwner(_ context.Context, ownerID ids.ID) (fx.Owner, error) { + b.ownersLock.RLock() + defer b.ownersLock.RUnlock() - owner, exists := b.subnetOwner[subnetID] + owner, exists := b.owners[ownerID] if !exists { return nil, database.ErrNotFound } return owner, nil } -func (b *backend) setSubnetOwner(subnetID ids.ID, owner fx.Owner) { - b.subnetOwnerLock.Lock() - defer b.subnetOwnerLock.Unlock() +func (b *backend) setOwner(ownerID ids.ID, owner fx.Owner) { + b.ownersLock.Lock() + defer b.ownersLock.Unlock() - b.subnetOwner[subnetID] = owner + b.owners[ownerID] = owner } diff --git a/wallet/chain/p/wallet/backend_visitor.go b/wallet/chain/p/wallet/backend_visitor.go index a1e399d6415f..beb9ded5e80a 100644 --- a/wallet/chain/p/wallet/backend_visitor.go +++ b/wallet/chain/p/wallet/backend_visitor.go @@ -11,6 +11,10 @@ import ( "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) var ( @@ -51,7 +55,7 @@ func (b *backendVisitor) CreateChainTx(tx *txs.CreateChainTx) error { } func (b *backendVisitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { - b.b.setSubnetOwner( + b.b.setOwner( b.txID, tx.Owner, ) @@ -63,7 +67,7 @@ func (b *backendVisitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx } func (b *backendVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { - b.b.setSubnetOwner( + b.b.setOwner( tx.Subnet, tx.Owner, ) @@ -75,6 +79,26 @@ func (b *backendVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { } func (b *backendVisitor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetValidatorTx) error { + warpMessage, err := warp.ParseMessage(tx.Message) + if err != nil { + return err + } + addressedCallPayload, err := payload.ParseAddressedCall(warpMessage.Payload) + if err != nil { + return err + } + registerSubnetValidatorMessage, err := message.ParseRegisterSubnetValidator(addressedCallPayload.Payload) + if err != nil { + return err + } + + b.b.setOwner( + registerSubnetValidatorMessage.ValidationID(), + &secp256k1fx.OutputOwners{ + Threshold: registerSubnetValidatorMessage.DisableOwner.Threshold, + Addrs: registerSubnetValidatorMessage.DisableOwner.Addresses, + }, + ) return b.baseTx(&tx.BaseTx) } @@ -86,6 +110,10 @@ func (b *backendVisitor) IncreaseBalanceTx(tx *txs.IncreaseBalanceTx) error { return b.baseTx(&tx.BaseTx) } +func (b *backendVisitor) DisableSubnetValidatorTx(tx *txs.DisableSubnetValidatorTx) error { + return b.baseTx(&tx.BaseTx) +} + func (b *backendVisitor) BaseTx(tx *txs.BaseTx) error { return b.baseTx(tx) } From fa12221e3179559e385f5549fc9373f40f1cbbc4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 12:44:38 -0400 Subject: [PATCH 111/199] Fix wallet --- wallet/chain/p/wallet/wallet.go | 6 +----- wallet/chain/p/wallet/with_options.go | 2 -- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index 7811ad3b6f8b..713fb6e25e7d 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -157,14 +157,11 @@ type Wallet interface { // - [balance] that the validator should allocate to continuous fees // - [proofOfPossession] is the BLS PoP for the key included in the Warp // message - // - [remainingBalanceOwner] specifies the owner to send any of the - // remaining balance after removing the continuous fee // - [message] is the Warp message that authorizes this validator to be // added IssueRegisterSubnetValidatorTx( balance uint64, proofOfPossession [bls.SignatureLen]byte, - remainingBalanceOwner *secp256k1fx.OutputOwners, message []byte, options ...common.Option, ) (*txs.Tx, error) @@ -426,11 +423,10 @@ func (w *wallet) IssueConvertSubnetTx( func (w *wallet) IssueRegisterSubnetValidatorTx( balance uint64, proofOfPossession [bls.SignatureLen]byte, - remainingBalanceOwner *secp256k1fx.OutputOwners, message []byte, options ...common.Option, ) (*txs.Tx, error) { - utx, err := w.builder.NewRegisterSubnetValidatorTx(balance, proofOfPossession, remainingBalanceOwner, message, options...) + utx, err := w.builder.NewRegisterSubnetValidatorTx(balance, proofOfPossession, message, options...) if err != nil { return nil, err } diff --git a/wallet/chain/p/wallet/with_options.go b/wallet/chain/p/wallet/with_options.go index ab456d7d469e..3b3b1fe7c5b8 100644 --- a/wallet/chain/p/wallet/with_options.go +++ b/wallet/chain/p/wallet/with_options.go @@ -163,14 +163,12 @@ func (w *withOptions) IssueConvertSubnetTx( func (w *withOptions) IssueRegisterSubnetValidatorTx( balance uint64, proofOfPossession [bls.SignatureLen]byte, - remainingBalanceOwner *secp256k1fx.OutputOwners, message []byte, options ...common.Option, ) (*txs.Tx, error) { return w.wallet.IssueRegisterSubnetValidatorTx( balance, proofOfPossession, - remainingBalanceOwner, message, common.UnionOptions(w.options, options)..., ) From 0ea3b86c39e53214f70476199e151e44def8803b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 12:59:56 -0400 Subject: [PATCH 112/199] Update wallet --- wallet/chain/p/builder/builder.go | 66 +++++++++++++++++++ .../chain/p/builder/builder_with_options.go | 10 +++ wallet/chain/p/wallet/wallet.go | 21 ++++++ wallet/chain/p/wallet/with_options.go | 10 +++ wallet/subnet/primary/wallet.go | 9 ++- 5 files changed, 114 insertions(+), 2 deletions(-) diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index 625af0e29e3c..8e2b075a3e9c 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -200,6 +200,15 @@ type Builder interface { options ...common.Option, ) (*txs.IncreaseBalanceTx, error) + // NewIncreaseBalanceTx disables a validator and returns the continuous fee + // to the remaining balance owner. + // + // - [validationID] of the validator to disable + NewDisableSubnetValidatorTx( + validationID ids.ID, + options ...common.Option, + ) (*txs.DisableSubnetValidatorTx, error) + // NewImportTx creates an import transaction that attempts to consume all // the available UTXOs and import the funds to [to]. // @@ -1053,6 +1062,63 @@ func (b *builder) NewIncreaseBalanceTx( return tx, b.initCtx(tx) } +func (b *builder) NewDisableSubnetValidatorTx( + validationID ids.ID, + options ...common.Option, +) (*txs.DisableSubnetValidatorTx, error) { + var ( + toBurn = map[ids.ID]uint64{} + toStake = map[ids.ID]uint64{} + ops = common.NewOptions(options) + ) + disableAuth, err := b.authorize(validationID, ops) + if err != nil { + return nil, err + } + + memo := ops.Memo() + memoComplexity := gas.Dimensions{ + gas.Bandwidth: uint64(len(memo)), + } + authComplexity, err := fee.AuthComplexity(disableAuth) + if err != nil { + return nil, err + } + + complexity, err := fee.IntrinsicDisableSubnetValidatorTxComplexities.Add( + &memoComplexity, + &authComplexity, + ) + if err != nil { + return nil, err + } + + inputs, outputs, _, err := b.spend( + toBurn, + toStake, + 0, + complexity, + nil, + ops, + ) + if err != nil { + return nil, err + } + + tx := &txs.DisableSubnetValidatorTx{ + BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ + NetworkID: b.context.NetworkID, + BlockchainID: constants.PlatformChainID, + Ins: inputs, + Outs: outputs, + Memo: memo, + }}, + ValidationID: validationID, + DisableAuth: disableAuth, + } + return tx, b.initCtx(tx) +} + func (b *builder) NewImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/chain/p/builder/builder_with_options.go b/wallet/chain/p/builder/builder_with_options.go index a4578ccafd57..ec52d99d7faf 100644 --- a/wallet/chain/p/builder/builder_with_options.go +++ b/wallet/chain/p/builder/builder_with_options.go @@ -208,6 +208,16 @@ func (b *builderWithOptions) NewIncreaseBalanceTx( ) } +func (b *builderWithOptions) NewDisableSubnetValidatorTx( + validationID ids.ID, + options ...common.Option, +) (*txs.DisableSubnetValidatorTx, error) { + return b.builder.NewDisableSubnetValidatorTx( + validationID, + common.UnionOptions(b.options, options)..., + ) +} + func (b *builderWithOptions) NewImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index 12101a306e3a..4c5fa0b86048 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -188,6 +188,16 @@ type Wallet interface { options ...common.Option, ) (*txs.Tx, error) + // IssueDisableSubnetValidatorTx creates, signs, and issues a transaction + // that disables a validator and returns the continuous fee to the + // remaining balance owner. + // + // - [validationID] of the validator to disable + IssueDisableSubnetValidatorTx( + validationID ids.ID, + options ...common.Option, + ) (*txs.Tx, error) + // IssueImportTx creates, signs, and issues an import transaction that // attempts to consume all the available UTXOs and import the funds to [to]. // @@ -478,6 +488,17 @@ func (w *wallet) IssueIncreaseBalanceTx( return w.IssueUnsignedTx(utx, options...) } +func (w *wallet) IssueDisableSubnetValidatorTx( + validationID ids.ID, + options ...common.Option, +) (*txs.Tx, error) { + utx, err := w.builder.NewDisableSubnetValidatorTx(validationID, options...) + if err != nil { + return nil, err + } + return w.IssueUnsignedTx(utx, options...) +} + func (w *wallet) IssueImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/chain/p/wallet/with_options.go b/wallet/chain/p/wallet/with_options.go index d9711686f25f..a5b2e17e9283 100644 --- a/wallet/chain/p/wallet/with_options.go +++ b/wallet/chain/p/wallet/with_options.go @@ -196,6 +196,16 @@ func (w *withOptions) IssueIncreaseBalanceTx( ) } +func (w *withOptions) IssueDisableSubnetValidatorTx( + validationID ids.ID, + options ...common.Option, +) (*txs.Tx, error) { + return w.wallet.IssueDisableSubnetValidatorTx( + validationID, + common.UnionOptions(w.options, options)..., + ) +} + func (w *withOptions) IssueImportTx( sourceChainID ids.ID, to *secp256k1fx.OutputOwners, diff --git a/wallet/subnet/primary/wallet.go b/wallet/subnet/primary/wallet.go index 2b35c944c39c..751cd03a3049 100644 --- a/wallet/subnet/primary/wallet.go +++ b/wallet/subnet/primary/wallet.go @@ -73,9 +73,12 @@ type WalletConfig struct { // Keys to use for signing all transactions. AVAXKeychain keychain.Keychain // required EthKeychain c.EthKeychain // required - // Subnet IDs that the wallet should know about to be able to - // generate transactions. + // Subnet IDs that the wallet should know about to be able to generate + // transactions. SubnetIDs []ids.ID // optional + // Validation IDs that the wallet should know about to be able to generate + // transactions. + ValidationIDs []ids.ID // optional } // MakeWallet returns a wallet that supports issuing transactions to the chains @@ -105,6 +108,8 @@ func MakeWallet(ctx context.Context, config *WalletConfig) (Wallet, error) { if err != nil { return nil, err } + // TODO: Fetch validation disableOwners + pUTXOs := common.NewChainUTXOs(constants.PlatformChainID, avaxState.UTXOs) pBackend := pwallet.NewBackend(avaxState.PCTX, pUTXOs, subnetOwners) pClient := p.NewClient(avaxState.PClient, pBackend) From 61333d61ba364a3442e6714caa92c58e725567e3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 13:06:09 -0400 Subject: [PATCH 113/199] Update convertSubnetTx owner backend --- wallet/chain/p/wallet/backend_visitor.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/wallet/chain/p/wallet/backend_visitor.go b/wallet/chain/p/wallet/backend_visitor.go index beb9ded5e80a..92d0ef3fa280 100644 --- a/wallet/chain/p/wallet/backend_visitor.go +++ b/wallet/chain/p/wallet/backend_visitor.go @@ -75,6 +75,15 @@ func (b *backendVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnersh } func (b *backendVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { + for i, vdr := range tx.Validators { + b.b.setOwner( + tx.Subnet.Prefix(uint64(i)), + &secp256k1fx.OutputOwners{ + Threshold: vdr.DeactivationOwner.Threshold, + Addrs: vdr.DeactivationOwner.Addresses, + }, + ) + } return b.baseTx(&tx.BaseTx) } From 891bca4232d3690c64248e376e1efd961fba94bc Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 15:25:01 -0400 Subject: [PATCH 114/199] Store conversionID --- vms/platformvm/service.go | 5 +- vms/platformvm/state/diff.go | 49 +- vms/platformvm/state/diff_test.go | 59 ++- vms/platformvm/state/mock_chain.go | 31 +- vms/platformvm/state/mock_diff.go | 31 +- vms/platformvm/state/mock_state.go | 45 +- vms/platformvm/state/state.go | 68 +-- vms/platformvm/state/state_test.go | 55 +- .../txs/executor/create_chain_test.go | 7 +- .../txs/executor/staker_tx_verification.go | 2 +- .../txs/executor/standard_tx_executor.go | 3 +- .../txs/executor/standard_tx_executor_test.go | 21 +- .../txs/executor/subnet_tx_verification.go | 2 +- x/merkledb/mock_db.go | 491 ++++++++++++++++++ 14 files changed, 706 insertions(+), 163 deletions(-) create mode 100644 x/merkledb/mock_db.go diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 91b7810d30df..c4779e85789e 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -440,6 +440,7 @@ type GetSubnetResponse struct { // subnet transformation tx ID for an elastic subnet SubnetTransformationTxID ids.ID `json:"subnetTransformationTxID"` // subnet manager information for a permissionless L1 + ConversionID ids.ID `json:"conversionID"` ManagerChainID ids.ID `json:"managerChainID"` ManagerAddress types.JSONByteSlice `json:"managerAddress"` } @@ -490,12 +491,14 @@ func (s *Service) GetSubnet(_ *http.Request, args *GetSubnetArgs, response *GetS return err } - switch chainID, addr, err := s.vm.state.GetSubnetManager(args.SubnetID); err { + switch conversionID, chainID, addr, err := s.vm.state.GetSubnetConversion(args.SubnetID); err { case nil: response.IsPermissioned = false + response.ConversionID = conversionID response.ManagerChainID = chainID response.ManagerAddress = addr case database.ErrNotFound: + response.ConversionID = ids.Empty response.ManagerChainID = ids.Empty response.ManagerAddress = []byte(nil) default: diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 5110aa1ea6e0..470d1ea1ff69 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -55,8 +55,8 @@ type diff struct { addedSubnetIDs []ids.ID // Subnet ID --> Owner of the subnet subnetOwners map[ids.ID]fx.Owner - // Subnet ID --> Manager of the subnet - subnetManagers map[ids.ID]chainIDAndAddr + // Subnet ID --> Conversion of the subnet + subnetConversions map[ids.ID]subnetConversion // Subnet ID --> Tx that transforms the subnet transformedSubnets map[ids.ID]*txs.Tx @@ -79,17 +79,17 @@ func NewDiff( return nil, fmt.Errorf("%w: %s", ErrMissingParentState, parentID) } return &diff{ - parentID: parentID, - stateVersions: stateVersions, - timestamp: parentState.GetTimestamp(), - feeState: parentState.GetFeeState(), - sovExcess: parentState.GetSoVExcess(), - accruedFees: parentState.GetAccruedFees(), - parentActiveSOVs: parentState.NumActiveSubnetOnlyValidators(), - expiryDiff: newExpiryDiff(), - sovDiff: newSubnetOnlyValidatorsDiff(), - subnetOwners: make(map[ids.ID]fx.Owner), - subnetManagers: make(map[ids.ID]chainIDAndAddr), + parentID: parentID, + stateVersions: stateVersions, + timestamp: parentState.GetTimestamp(), + feeState: parentState.GetFeeState(), + sovExcess: parentState.GetSoVExcess(), + accruedFees: parentState.GetAccruedFees(), + parentActiveSOVs: parentState.NumActiveSubnetOnlyValidators(), + expiryDiff: newExpiryDiff(), + sovDiff: newSubnetOnlyValidatorsDiff(), + subnetOwners: make(map[ids.ID]fx.Owner), + subnetConversions: make(map[ids.ID]subnetConversion), }, nil } @@ -435,23 +435,24 @@ func (d *diff) SetSubnetOwner(subnetID ids.ID, owner fx.Owner) { d.subnetOwners[subnetID] = owner } -func (d *diff) GetSubnetManager(subnetID ids.ID) (ids.ID, []byte, error) { - if manager, exists := d.subnetManagers[subnetID]; exists { - return manager.ChainID, manager.Addr, nil +func (d *diff) GetSubnetConversion(subnetID ids.ID) (ids.ID, ids.ID, []byte, error) { + if conversion, exists := d.subnetConversions[subnetID]; exists { + return conversion.ConversionID, conversion.ChainID, conversion.Addr, nil } // If the subnet manager was not assigned in this diff, ask the parent state. parentState, ok := d.stateVersions.GetState(d.parentID) if !ok { - return ids.Empty, nil, ErrMissingParentState + return ids.Empty, ids.Empty, nil, ErrMissingParentState } - return parentState.GetSubnetManager(subnetID) + return parentState.GetSubnetConversion(subnetID) } -func (d *diff) SetSubnetManager(subnetID ids.ID, chainID ids.ID, addr []byte) { - d.subnetManagers[subnetID] = chainIDAndAddr{ - ChainID: chainID, - Addr: addr, +func (d *diff) SetSubnetConversion(subnetID ids.ID, conversionID ids.ID, chainID ids.ID, addr []byte) { + d.subnetConversions[subnetID] = subnetConversion{ + ConversionID: conversionID, + ChainID: chainID, + Addr: addr, } } @@ -675,8 +676,8 @@ func (d *diff) Apply(baseState Chain) error { for subnetID, owner := range d.subnetOwners { baseState.SetSubnetOwner(subnetID, owner) } - for subnetID, manager := range d.subnetManagers { - baseState.SetSubnetManager(subnetID, manager.ChainID, manager.Addr) + for subnetID, conversion := range d.subnetConversions { + baseState.SetSubnetConversion(subnetID, conversion.ConversionID, conversion.ChainID, conversion.Addr) } return nil } diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index 235b8ad4e0a0..cbbf3c12a4a5 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -897,44 +897,63 @@ func TestDiffSubnetOwner(t *testing.T) { func TestDiffSubnetManager(t *testing.T) { var ( - require = require.New(t) - state = newTestState(t, memdb.New()) - newManager = chainIDAndAddr{ids.GenerateTestID(), []byte{1, 2, 3, 4}} - subnetID = ids.GenerateTestID() + require = require.New(t) + state = newTestState(t, memdb.New()) + subnetID = ids.GenerateTestID() + expectedConversion = subnetConversion{ + ConversionID: ids.GenerateTestID(), + ChainID: ids.GenerateTestID(), + Addr: []byte{1, 2, 3, 4}, + } ) - chainID, addr, err := state.GetSubnetManager(subnetID) + conversionID, chainID, addr, err := state.GetSubnetConversion(subnetID) require.ErrorIs(err, database.ErrNotFound) - require.Equal(ids.Empty, chainID) - require.Nil(addr) + require.Zero(conversionID) + require.Zero(chainID) + require.Zero(addr) d, err := NewDiffOn(state) require.NoError(err) - chainID, addr, err = d.GetSubnetManager(subnetID) + conversionID, chainID, addr, err = state.GetSubnetConversion(subnetID) require.ErrorIs(err, database.ErrNotFound) - require.Equal(ids.Empty, chainID) - require.Nil(addr) + require.Zero(conversionID) + require.Zero(chainID) + require.Zero(addr) // Setting a subnet manager should be reflected on diff not state - d.SetSubnetManager(subnetID, newManager.ChainID, newManager.Addr) - chainID, addr, err = d.GetSubnetManager(subnetID) + d.SetSubnetConversion(subnetID, expectedConversion.ConversionID, expectedConversion.ChainID, expectedConversion.Addr) + conversionID, chainID, addr, err = d.GetSubnetConversion(subnetID) require.NoError(err) - require.Equal(newManager.ChainID, chainID) - require.Equal(newManager.Addr, addr) + require.Equal( + expectedConversion, + subnetConversion{ + ConversionID: conversionID, + ChainID: chainID, + Addr: addr, + }, + ) - chainID, addr, err = state.GetSubnetManager(subnetID) + conversionID, chainID, addr, err = state.GetSubnetConversion(subnetID) require.ErrorIs(err, database.ErrNotFound) - require.Equal(ids.Empty, chainID) - require.Nil(addr) + require.Zero(conversionID) + require.Zero(chainID) + require.Zero(addr) // State should reflect new subnet manager after diff is applied require.NoError(d.Apply(state)) - chainID, addr, err = state.GetSubnetManager(subnetID) + conversionID, chainID, addr, err = state.GetSubnetConversion(subnetID) require.NoError(err) - require.Equal(newManager.ChainID, chainID) - require.Equal(newManager.Addr, addr) + require.Equal( + expectedConversion, + subnetConversion{ + ConversionID: conversionID, + ChainID: chainID, + Addr: addr, + }, + ) } func TestDiffStacking(t *testing.T) { diff --git a/vms/platformvm/state/mock_chain.go b/vms/platformvm/state/mock_chain.go index 4d34407ee3c2..a290089d464e 100644 --- a/vms/platformvm/state/mock_chain.go +++ b/vms/platformvm/state/mock_chain.go @@ -382,20 +382,21 @@ func (mr *MockChainMockRecorder) GetSoVExcess() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSoVExcess", reflect.TypeOf((*MockChain)(nil).GetSoVExcess)) } -// GetSubnetManager mocks base method. -func (m *MockChain) GetSubnetManager(subnetID ids.ID) (ids.ID, []byte, error) { +// GetSubnetConversion mocks base method. +func (m *MockChain) GetSubnetConversion(subnetID ids.ID) (ids.ID, ids.ID, []byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSubnetManager", subnetID) + ret := m.ctrl.Call(m, "GetSubnetConversion", subnetID) ret0, _ := ret[0].(ids.ID) - ret1, _ := ret[1].([]byte) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 + ret1, _ := ret[1].(ids.ID) + ret2, _ := ret[2].([]byte) + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 } -// GetSubnetManager indicates an expected call of GetSubnetManager. -func (mr *MockChainMockRecorder) GetSubnetManager(subnetID any) *gomock.Call { +// GetSubnetConversion indicates an expected call of GetSubnetConversion. +func (mr *MockChainMockRecorder) GetSubnetConversion(subnetID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetManager", reflect.TypeOf((*MockChain)(nil).GetSubnetManager), subnetID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetConversion", reflect.TypeOf((*MockChain)(nil).GetSubnetConversion), subnetID) } // GetSubnetOnlyValidator mocks base method. @@ -672,16 +673,16 @@ func (mr *MockChainMockRecorder) SetSoVExcess(e any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSoVExcess", reflect.TypeOf((*MockChain)(nil).SetSoVExcess), e) } -// SetSubnetManager mocks base method. -func (m *MockChain) SetSubnetManager(subnetID, chainID ids.ID, addr []byte) { +// SetSubnetConversion mocks base method. +func (m *MockChain) SetSubnetConversion(subnetID, conversionID, chainID ids.ID, addr []byte) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SetSubnetManager", subnetID, chainID, addr) + m.ctrl.Call(m, "SetSubnetConversion", subnetID, conversionID, chainID, addr) } -// SetSubnetManager indicates an expected call of SetSubnetManager. -func (mr *MockChainMockRecorder) SetSubnetManager(subnetID, chainID, addr any) *gomock.Call { +// SetSubnetConversion indicates an expected call of SetSubnetConversion. +func (mr *MockChainMockRecorder) SetSubnetConversion(subnetID, conversionID, chainID, addr any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetManager", reflect.TypeOf((*MockChain)(nil).SetSubnetManager), subnetID, chainID, addr) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetConversion", reflect.TypeOf((*MockChain)(nil).SetSubnetConversion), subnetID, conversionID, chainID, addr) } // SetSubnetOwner mocks base method. diff --git a/vms/platformvm/state/mock_diff.go b/vms/platformvm/state/mock_diff.go index 95be0ff1fb5e..1d150e3310b9 100644 --- a/vms/platformvm/state/mock_diff.go +++ b/vms/platformvm/state/mock_diff.go @@ -396,20 +396,21 @@ func (mr *MockDiffMockRecorder) GetSoVExcess() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSoVExcess", reflect.TypeOf((*MockDiff)(nil).GetSoVExcess)) } -// GetSubnetManager mocks base method. -func (m *MockDiff) GetSubnetManager(subnetID ids.ID) (ids.ID, []byte, error) { +// GetSubnetConversion mocks base method. +func (m *MockDiff) GetSubnetConversion(subnetID ids.ID) (ids.ID, ids.ID, []byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSubnetManager", subnetID) + ret := m.ctrl.Call(m, "GetSubnetConversion", subnetID) ret0, _ := ret[0].(ids.ID) - ret1, _ := ret[1].([]byte) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 + ret1, _ := ret[1].(ids.ID) + ret2, _ := ret[2].([]byte) + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 } -// GetSubnetManager indicates an expected call of GetSubnetManager. -func (mr *MockDiffMockRecorder) GetSubnetManager(subnetID any) *gomock.Call { +// GetSubnetConversion indicates an expected call of GetSubnetConversion. +func (mr *MockDiffMockRecorder) GetSubnetConversion(subnetID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetManager", reflect.TypeOf((*MockDiff)(nil).GetSubnetManager), subnetID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetConversion", reflect.TypeOf((*MockDiff)(nil).GetSubnetConversion), subnetID) } // GetSubnetOnlyValidator mocks base method. @@ -686,16 +687,16 @@ func (mr *MockDiffMockRecorder) SetSoVExcess(e any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSoVExcess", reflect.TypeOf((*MockDiff)(nil).SetSoVExcess), e) } -// SetSubnetManager mocks base method. -func (m *MockDiff) SetSubnetManager(subnetID, chainID ids.ID, addr []byte) { +// SetSubnetConversion mocks base method. +func (m *MockDiff) SetSubnetConversion(subnetID, conversionID, chainID ids.ID, addr []byte) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SetSubnetManager", subnetID, chainID, addr) + m.ctrl.Call(m, "SetSubnetConversion", subnetID, conversionID, chainID, addr) } -// SetSubnetManager indicates an expected call of SetSubnetManager. -func (mr *MockDiffMockRecorder) SetSubnetManager(subnetID, chainID, addr any) *gomock.Call { +// SetSubnetConversion indicates an expected call of SetSubnetConversion. +func (mr *MockDiffMockRecorder) SetSubnetConversion(subnetID, conversionID, chainID, addr any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetManager", reflect.TypeOf((*MockDiff)(nil).SetSubnetManager), subnetID, chainID, addr) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetConversion", reflect.TypeOf((*MockDiff)(nil).SetSubnetConversion), subnetID, conversionID, chainID, addr) } // SetSubnetOwner mocks base method. diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index 41d06025d3f8..1352236f7e97 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -586,6 +586,23 @@ func (mr *MockStateMockRecorder) GetStatelessBlock(blockID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatelessBlock", reflect.TypeOf((*MockState)(nil).GetStatelessBlock), blockID) } +// GetSubnetConversion mocks base method. +func (m *MockState) GetSubnetConversion(subnetID ids.ID) (ids.ID, ids.ID, []byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSubnetConversion", subnetID) + ret0, _ := ret[0].(ids.ID) + ret1, _ := ret[1].(ids.ID) + ret2, _ := ret[2].([]byte) + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 +} + +// GetSubnetConversion indicates an expected call of GetSubnetConversion. +func (mr *MockStateMockRecorder) GetSubnetConversion(subnetID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetConversion", reflect.TypeOf((*MockState)(nil).GetSubnetConversion), subnetID) +} + // GetSubnetIDs mocks base method. func (m *MockState) GetSubnetIDs() ([]ids.ID, error) { m.ctrl.T.Helper() @@ -601,22 +618,6 @@ func (mr *MockStateMockRecorder) GetSubnetIDs() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetIDs", reflect.TypeOf((*MockState)(nil).GetSubnetIDs)) } -// GetSubnetManager mocks base method. -func (m *MockState) GetSubnetManager(subnetID ids.ID) (ids.ID, []byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSubnetManager", subnetID) - ret0, _ := ret[0].(ids.ID) - ret1, _ := ret[1].([]byte) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// GetSubnetManager indicates an expected call of GetSubnetManager. -func (mr *MockStateMockRecorder) GetSubnetManager(subnetID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetManager", reflect.TypeOf((*MockState)(nil).GetSubnetManager), subnetID) -} - // GetSubnetOnlyValidator mocks base method. func (m *MockState) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { m.ctrl.T.Helper() @@ -945,16 +946,16 @@ func (mr *MockStateMockRecorder) SetSoVExcess(e any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSoVExcess", reflect.TypeOf((*MockState)(nil).SetSoVExcess), e) } -// SetSubnetManager mocks base method. -func (m *MockState) SetSubnetManager(subnetID, chainID ids.ID, addr []byte) { +// SetSubnetConversion mocks base method. +func (m *MockState) SetSubnetConversion(subnetID, conversionID, chainID ids.ID, addr []byte) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SetSubnetManager", subnetID, chainID, addr) + m.ctrl.Call(m, "SetSubnetConversion", subnetID, conversionID, chainID, addr) } -// SetSubnetManager indicates an expected call of SetSubnetManager. -func (mr *MockStateMockRecorder) SetSubnetManager(subnetID, chainID, addr any) *gomock.Call { +// SetSubnetConversion indicates an expected call of SetSubnetConversion. +func (mr *MockStateMockRecorder) SetSubnetConversion(subnetID, conversionID, chainID, addr any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetManager", reflect.TypeOf((*MockState)(nil).SetSubnetManager), subnetID, chainID, addr) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetConversion", reflect.TypeOf((*MockState)(nil).SetSubnetConversion), subnetID, conversionID, chainID, addr) } // SetSubnetOwner mocks base method. diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 12405f4ab60e..77698f07449e 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -135,8 +135,8 @@ type Chain interface { GetSubnetOwner(subnetID ids.ID) (fx.Owner, error) SetSubnetOwner(subnetID ids.ID, owner fx.Owner) - GetSubnetManager(subnetID ids.ID) (ids.ID, []byte, error) - SetSubnetManager(subnetID ids.ID, chainID ids.ID, addr []byte) + GetSubnetConversion(subnetID ids.ID) (ids.ID, ids.ID, []byte, error) + SetSubnetConversion(subnetID ids.ID, conversionID ids.ID, chainID ids.ID, addr []byte) GetSubnetTransformation(subnetID ids.ID) (*txs.Tx, error) AddSubnetTransformation(transformSubnetTx *txs.Tx) @@ -387,9 +387,9 @@ type state struct { subnetOwnerCache cache.Cacher[ids.ID, fxOwnerAndSize] // cache of subnetID -> owner; if the entry is nil, it is not in the database subnetOwnerDB database.Database - subnetManagers map[ids.ID]chainIDAndAddr // map of subnetID -> manager of the subnet - subnetManagerCache cache.Cacher[ids.ID, chainIDAndAddr] // cache of subnetID -> manager - subnetManagerDB database.Database + subnetConversions map[ids.ID]subnetConversion // map of subnetID -> manager of the subnet + subnetConversionCache cache.Cacher[ids.ID, subnetConversion] // cache of subnetID -> manager + subnetConversionDB database.Database transformedSubnets map[ids.ID]*txs.Tx // map of subnetID -> transformSubnetTx transformedSubnetCache cache.Cacher[ids.ID, *txs.Tx] // cache of subnetID -> transformSubnetTx; if the entry is nil, it is not in the database @@ -463,9 +463,10 @@ type fxOwnerAndSize struct { size int } -type chainIDAndAddr struct { - ChainID ids.ID `serialize:"true"` - Addr []byte `serialize:"true"` +type subnetConversion struct { + ConversionID ids.ID `serialize:"true"` + ChainID ids.ID `serialize:"true"` + Addr []byte `serialize:"true"` } func txSize(_ ids.ID, tx *txs.Tx) int { @@ -579,10 +580,10 @@ func New( } subnetManagerDB := prefixdb.New(SubnetManagerPrefix, baseDB) - subnetManagerCache, err := metercacher.New[ids.ID, chainIDAndAddr]( + subnetManagerCache, err := metercacher.New[ids.ID, subnetConversion]( "subnet_manager_cache", metricsReg, - cache.NewSizedLRU[ids.ID, chainIDAndAddr](execCfg.SubnetManagerCacheSize, func(_ ids.ID, f chainIDAndAddr) int { + cache.NewSizedLRU[ids.ID, subnetConversion](execCfg.SubnetManagerCacheSize, func(_ ids.ID, f subnetConversion) int { return 2*ids.IDLen + len(f.Addr) }), ) @@ -701,9 +702,9 @@ func New( subnetOwnerDB: subnetOwnerDB, subnetOwnerCache: subnetOwnerCache, - subnetManagers: make(map[ids.ID]chainIDAndAddr), - subnetManagerDB: subnetManagerDB, - subnetManagerCache: subnetManagerCache, + subnetConversions: make(map[ids.ID]subnetConversion), + subnetConversionDB: subnetManagerDB, + subnetConversionCache: subnetManagerCache, transformedSubnets: make(map[ids.ID]*txs.Tx), transformedSubnetCache: transformedSubnetCache, @@ -950,32 +951,33 @@ func (s *state) SetSubnetOwner(subnetID ids.ID, owner fx.Owner) { s.subnetOwners[subnetID] = owner } -func (s *state) GetSubnetManager(subnetID ids.ID) (ids.ID, []byte, error) { - if chainIDAndAddr, exists := s.subnetManagers[subnetID]; exists { - return chainIDAndAddr.ChainID, chainIDAndAddr.Addr, nil +func (s *state) GetSubnetConversion(subnetID ids.ID) (ids.ID, ids.ID, []byte, error) { + if conversion, exists := s.subnetConversions[subnetID]; exists { + return conversion.ConversionID, conversion.ChainID, conversion.Addr, nil } - if chainIDAndAddr, cached := s.subnetManagerCache.Get(subnetID); cached { - return chainIDAndAddr.ChainID, chainIDAndAddr.Addr, nil + if conversion, cached := s.subnetConversionCache.Get(subnetID); cached { + return conversion.ConversionID, conversion.ChainID, conversion.Addr, nil } - chainIDAndAddrBytes, err := s.subnetManagerDB.Get(subnetID[:]) + conversionBytes, err := s.subnetConversionDB.Get(subnetID[:]) if err != nil { - return ids.Empty, nil, err + return ids.Empty, ids.Empty, nil, err } - var manager chainIDAndAddr - if _, err := block.GenesisCodec.Unmarshal(chainIDAndAddrBytes, &manager); err != nil { - return ids.Empty, nil, err + var conversion subnetConversion + if _, err := block.GenesisCodec.Unmarshal(conversionBytes, &conversion); err != nil { + return ids.Empty, ids.Empty, nil, err } - s.subnetManagerCache.Put(subnetID, manager) - return manager.ChainID, manager.Addr, nil + s.subnetConversionCache.Put(subnetID, conversion) + return conversion.ConversionID, conversion.ChainID, conversion.Addr, nil } -func (s *state) SetSubnetManager(subnetID ids.ID, chainID ids.ID, addr []byte) { - s.subnetManagers[subnetID] = chainIDAndAddr{ - ChainID: chainID, - Addr: addr, +func (s *state) SetSubnetConversion(subnetID ids.ID, conversionID ids.ID, chainID ids.ID, addr []byte) { + s.subnetConversions[subnetID] = subnetConversion{ + ConversionID: conversionID, + ChainID: chainID, + Addr: addr, } } @@ -2916,19 +2918,19 @@ func (s *state) writeSubnetOwners() error { } func (s *state) writeSubnetManagers() error { - for subnetID, manager := range s.subnetManagers { + for subnetID, manager := range s.subnetConversions { subnetID := subnetID manager := manager - delete(s.subnetManagers, subnetID) + delete(s.subnetConversions, subnetID) managerBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &manager) if err != nil { return fmt.Errorf("failed to marshal subnet manager: %w", err) } - s.subnetManagerCache.Put(subnetID, manager) + s.subnetConversionCache.Put(subnetID, manager) - if err := s.subnetManagerDB.Put(subnetID[:], managerBytes); err != nil { + if err := s.subnetConversionDB.Put(subnetID[:], managerBytes); err != nil { return fmt.Errorf("failed to write subnet manager: %w", err) } } diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index d0db7bd6d11c..294b3e943e9b 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1106,23 +1106,24 @@ func TestStateSubnetOwner(t *testing.T) { func TestStateSubnetManager(t *testing.T) { tests := []struct { name string - setup func(t *testing.T, s State, subnetID ids.ID, chainID ids.ID, addr []byte) + setup func(t *testing.T, s State, subnetID ids.ID, conversionID ids.ID, chainID ids.ID, addr []byte) }{ { name: "in-memory", - setup: func(_ *testing.T, s State, subnetID ids.ID, chainID ids.ID, addr []byte) { - s.SetSubnetManager(subnetID, chainID, addr) + setup: func(_ *testing.T, s State, subnetID ids.ID, conversionID ids.ID, chainID ids.ID, addr []byte) { + s.SetSubnetConversion(subnetID, conversionID, chainID, addr) }, }, { name: "cache", - setup: func(t *testing.T, s State, subnetID ids.ID, chainID ids.ID, addr []byte) { - subnetManagerCache := s.(*state).subnetManagerCache + setup: func(t *testing.T, s State, subnetID ids.ID, conversionID ids.ID, chainID ids.ID, addr []byte) { + subnetManagerCache := s.(*state).subnetConversionCache require.Zero(t, subnetManagerCache.Len()) - subnetManagerCache.Put(subnetID, chainIDAndAddr{ - ChainID: chainID, - Addr: addr, + subnetManagerCache.Put(subnetID, subnetConversion{ + ConversionID: conversionID, + ChainID: chainID, + Addr: addr, }) require.Equal(t, 1, subnetManagerCache.Len()) }, @@ -1130,25 +1131,35 @@ func TestStateSubnetManager(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - initializedState := newTestState(t, memdb.New()) + var ( + require = require.New(t) + state = newTestState(t, memdb.New()) + subnetID = ids.GenerateTestID() + expectedConversion = subnetConversion{ + ConversionID: ids.GenerateTestID(), + ChainID: ids.GenerateTestID(), + Addr: []byte{'a', 'd', 'd', 'r'}, + } + ) - subnetID := ids.GenerateTestID() - chainID, addr, err := initializedState.GetSubnetManager(subnetID) + conversionID, chainID, addr, err := state.GetSubnetConversion(subnetID) require.ErrorIs(err, database.ErrNotFound) - require.Equal(ids.Empty, chainID) - require.Nil(addr) - - expectedChainID := ids.GenerateTestID() - expectedAddr := []byte{'a', 'd', 'd', 'r'} + require.Zero(conversionID) + require.Zero(chainID) + require.Zero(addr) - test.setup(t, initializedState, subnetID, expectedChainID, expectedAddr) + test.setup(t, state, subnetID, expectedConversion.ConversionID, expectedConversion.ChainID, expectedConversion.Addr) - chainID, addr, err = initializedState.GetSubnetManager(subnetID) + conversionID, chainID, addr, err = state.GetSubnetConversion(subnetID) require.NoError(err) - require.Equal(expectedChainID, chainID) - require.Equal(expectedAddr, addr) + require.Equal( + expectedConversion, + subnetConversion{ + ConversionID: conversionID, + ChainID: chainID, + Addr: addr, + }, + ) }) } } diff --git a/vms/platformvm/txs/executor/create_chain_test.go b/vms/platformvm/txs/executor/create_chain_test.go index 61fead2677a7..cf3e0e8d2cf2 100644 --- a/vms/platformvm/txs/executor/create_chain_test.go +++ b/vms/platformvm/txs/executor/create_chain_test.go @@ -286,7 +286,12 @@ func TestEtnaCreateChainTxInvalidWithManagedSubnet(t *testing.T) { builderDiff, err := state.NewDiffOn(stateDiff) require.NoError(err) - stateDiff.SetSubnetManager(subnetID, ids.GenerateTestID(), []byte{'a', 'd', 'd', 'r', 'e', 's', 's'}) + stateDiff.SetSubnetConversion( + subnetID, + ids.GenerateTestID(), + ids.GenerateTestID(), + []byte{'a', 'd', 'd', 'r', 'e', 's', 's'}, + ) feeCalculator := state.PickFeeCalculator(env.config, builderDiff) executor := StandardTxExecutor{ diff --git a/vms/platformvm/txs/executor/staker_tx_verification.go b/vms/platformvm/txs/executor/staker_tx_verification.go index d458c01ab259..69b8cd567586 100644 --- a/vms/platformvm/txs/executor/staker_tx_verification.go +++ b/vms/platformvm/txs/executor/staker_tx_verification.go @@ -308,7 +308,7 @@ func verifyRemoveSubnetValidatorTx( } if backend.Config.UpgradeConfig.IsEtnaActivated(currentTimestamp) { - _, _, err := chainState.GetSubnetManager(tx.Subnet) + _, _, _, err := chainState.GetSubnetConversion(tx.Subnet) if err == nil { return nil, false, fmt.Errorf("%w: %q", ErrRemoveValidatorManagedSubnet, tx.Subnet) } diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index d0a37f5ba82c..14069b1e4e43 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -542,7 +542,8 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { // Produce the UTXOS avax.Produce(e.State, txID, tx.Outs) // Set the new Subnet manager in the database - e.State.SetSubnetManager(tx.Subnet, tx.ChainID, tx.Address) + // TODO: Populate the conversionID + e.State.SetSubnetConversion(tx.Subnet, ids.Empty, tx.ChainID, tx.Address) return nil } diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index e11ad73ac5d9..4dbd740f9a7b 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -913,7 +913,12 @@ func TestEtnaStandardTxExecutorAddSubnetValidator(t *testing.T) { onAcceptState, err := state.NewDiff(lastAcceptedID, env) require.NoError(err) - onAcceptState.SetSubnetManager(subnetID, ids.GenerateTestID(), []byte{'a', 'd', 'd', 'r', 'e', 's', 's'}) + onAcceptState.SetSubnetConversion( + subnetID, + ids.GenerateTestID(), + ids.GenerateTestID(), + []byte{'a', 'd', 'd', 'r', 'e', 's', 's'}, + ) executor := StandardTxExecutor{ Backend: &env.backend, @@ -1994,7 +1999,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { name: "attempted to remove subnet validator after subnet manager is set", newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) - env.state.EXPECT().GetSubnetManager(env.unsignedTx.Subnet).Return(ids.GenerateTestID(), []byte{'a', 'd', 'd', 'r', 'e', 's', 's'}, nil).AnyTimes() + env.state.EXPECT().GetSubnetConversion(env.unsignedTx.Subnet).Return(ids.GenerateTestID(), ids.GenerateTestID(), []byte{'a', 'd', 'd', 'r', 'e', 's', 's'}, nil).AnyTimes() env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() cfg := &config.Config{ @@ -2245,7 +2250,7 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { subnetOwner := fxmock.NewOwner(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil) - env.state.EXPECT().GetSubnetManager(env.unsignedTx.Subnet).Return(ids.Empty, nil, database.ErrNotFound).Times(1) + env.state.EXPECT().GetSubnetConversion(env.unsignedTx.Subnet).Return(ids.Empty, ids.Empty, nil, database.ErrNotFound).Times(1) env.state.EXPECT().GetSubnetTransformation(env.unsignedTx.Subnet).Return(nil, database.ErrNotFound).Times(1) env.fx.EXPECT().VerifyPermission(gomock.Any(), env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(nil) env.flowChecker.EXPECT().VerifySpend( @@ -2284,7 +2289,7 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { subnetOwner := fxmock.NewOwner(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil).Times(1) - env.state.EXPECT().GetSubnetManager(env.unsignedTx.Subnet).Return(ids.GenerateTestID(), make([]byte, 20), nil) + env.state.EXPECT().GetSubnetConversion(env.unsignedTx.Subnet).Return(ids.GenerateTestID(), ids.GenerateTestID(), make([]byte, 20), nil) env.state.EXPECT().GetSubnetTransformation(env.unsignedTx.Subnet).Return(nil, database.ErrNotFound).Times(1) env.fx.EXPECT().VerifyPermission(env.unsignedTx, env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(nil).Times(1) @@ -2319,7 +2324,7 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { subnetOwner := fxmock.NewOwner(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil).Times(1) - env.state.EXPECT().GetSubnetManager(env.unsignedTx.Subnet).Return(ids.Empty, nil, database.ErrNotFound).Times(1) + env.state.EXPECT().GetSubnetConversion(env.unsignedTx.Subnet).Return(ids.Empty, ids.Empty, nil, database.ErrNotFound).Times(1) env.state.EXPECT().GetSubnetTransformation(env.unsignedTx.Subnet).Return(nil, database.ErrNotFound).Times(1) env.fx.EXPECT().VerifyPermission(env.unsignedTx, env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(nil).Times(1) env.flowChecker.EXPECT().VerifySpend( @@ -2478,7 +2483,7 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { { name: "invalid if subnet is converted", updateExecutor: func(e *StandardTxExecutor) { - e.State.SetSubnetManager(subnetID, ids.GenerateTestID(), nil) + e.State.SetSubnetConversion(subnetID, ids.GenerateTestID(), ids.GenerateTestID(), nil) }, expectedErr: errIsImmutable, }, @@ -2566,8 +2571,10 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { require.Equal(expectedUTXO, utxo) } - stateChainID, stateAddress, err := diff.GetSubnetManager(subnetID) + // TODO: Populate the conversionID + stateConversionID, stateChainID, stateAddress, err := diff.GetSubnetConversion(subnetID) require.NoError(err) + require.Zero(stateConversionID) require.Equal(chainID, stateChainID) require.Equal(address, stateAddress) }) diff --git a/vms/platformvm/txs/executor/subnet_tx_verification.go b/vms/platformvm/txs/executor/subnet_tx_verification.go index 6e5ecb9a34f5..7466fd78227e 100644 --- a/vms/platformvm/txs/executor/subnet_tx_verification.go +++ b/vms/platformvm/txs/executor/subnet_tx_verification.go @@ -43,7 +43,7 @@ func verifyPoASubnetAuthorization( return nil, err } - _, _, err = chainState.GetSubnetManager(subnetID) + _, _, _, err = chainState.GetSubnetConversion(subnetID) if err == nil { return nil, fmt.Errorf("%q %w", subnetID, errIsImmutable) } diff --git a/x/merkledb/mock_db.go b/x/merkledb/mock_db.go new file mode 100644 index 000000000000..c3bf69cf22f6 --- /dev/null +++ b/x/merkledb/mock_db.go @@ -0,0 +1,491 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: x/merkledb/db.go +// +// Generated by this command: +// +// mockgen -source=x/merkledb/db.go -destination=x/merkledb/mock_db.go -package=merkledb -exclude_interfaces=ChangeProofer,RangeProofer,Clearer,Prefetcher -mock_names=MockMerkleDB=MockMerkleDB +// + +// Package merkledb is a generated GoMock package. +package merkledb + +import ( + context "context" + reflect "reflect" + + database "github.com/ava-labs/avalanchego/database" + ids "github.com/ava-labs/avalanchego/ids" + maybe "github.com/ava-labs/avalanchego/utils/maybe" + gomock "go.uber.org/mock/gomock" +) + +// MockMerkleDB is a mock of MerkleDB interface. +type MockMerkleDB struct { + ctrl *gomock.Controller + recorder *MockMerkleDBMockRecorder +} + +// MockMerkleDBMockRecorder is the mock recorder for MockMerkleDB. +type MockMerkleDBMockRecorder struct { + mock *MockMerkleDB +} + +// NewMockMerkleDB creates a new mock instance. +func NewMockMerkleDB(ctrl *gomock.Controller) *MockMerkleDB { + mock := &MockMerkleDB{ctrl: ctrl} + mock.recorder = &MockMerkleDBMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMerkleDB) EXPECT() *MockMerkleDBMockRecorder { + return m.recorder +} + +// Clear mocks base method. +func (m *MockMerkleDB) Clear() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Clear") + ret0, _ := ret[0].(error) + return ret0 +} + +// Clear indicates an expected call of Clear. +func (mr *MockMerkleDBMockRecorder) Clear() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockMerkleDB)(nil).Clear)) +} + +// Close mocks base method. +func (m *MockMerkleDB) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close. +func (mr *MockMerkleDBMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockMerkleDB)(nil).Close)) +} + +// CommitChangeProof mocks base method. +func (m *MockMerkleDB) CommitChangeProof(ctx context.Context, proof *ChangeProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CommitChangeProof", ctx, proof) + ret0, _ := ret[0].(error) + return ret0 +} + +// CommitChangeProof indicates an expected call of CommitChangeProof. +func (mr *MockMerkleDBMockRecorder) CommitChangeProof(ctx, proof any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).CommitChangeProof), ctx, proof) +} + +// CommitRangeProof mocks base method. +func (m *MockMerkleDB) CommitRangeProof(ctx context.Context, start, end maybe.Maybe[[]byte], proof *RangeProof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CommitRangeProof", ctx, start, end, proof) + ret0, _ := ret[0].(error) + return ret0 +} + +// CommitRangeProof indicates an expected call of CommitRangeProof. +func (mr *MockMerkleDBMockRecorder) CommitRangeProof(ctx, start, end, proof any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitRangeProof", reflect.TypeOf((*MockMerkleDB)(nil).CommitRangeProof), ctx, start, end, proof) +} + +// Compact mocks base method. +func (m *MockMerkleDB) Compact(start, limit []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Compact", start, limit) + ret0, _ := ret[0].(error) + return ret0 +} + +// Compact indicates an expected call of Compact. +func (mr *MockMerkleDBMockRecorder) Compact(start, limit any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Compact", reflect.TypeOf((*MockMerkleDB)(nil).Compact), start, limit) +} + +// Delete mocks base method. +func (m *MockMerkleDB) Delete(key []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", key) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockMerkleDBMockRecorder) Delete(key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockMerkleDB)(nil).Delete), key) +} + +// Get mocks base method. +func (m *MockMerkleDB) Get(key []byte) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", key) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockMerkleDBMockRecorder) Get(key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockMerkleDB)(nil).Get), key) +} + +// GetChangeProof mocks base method. +func (m *MockMerkleDB) GetChangeProof(ctx context.Context, startRootID, endRootID ids.ID, start, end maybe.Maybe[[]byte], maxLength int) (*ChangeProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetChangeProof", ctx, startRootID, endRootID, start, end, maxLength) + ret0, _ := ret[0].(*ChangeProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetChangeProof indicates an expected call of GetChangeProof. +func (mr *MockMerkleDBMockRecorder) GetChangeProof(ctx, startRootID, endRootID, start, end, maxLength any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).GetChangeProof), ctx, startRootID, endRootID, start, end, maxLength) +} + +// GetMerkleRoot mocks base method. +func (m *MockMerkleDB) GetMerkleRoot(ctx context.Context) (ids.ID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMerkleRoot", ctx) + ret0, _ := ret[0].(ids.ID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMerkleRoot indicates an expected call of GetMerkleRoot. +func (mr *MockMerkleDBMockRecorder) GetMerkleRoot(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMerkleRoot", reflect.TypeOf((*MockMerkleDB)(nil).GetMerkleRoot), ctx) +} + +// GetProof mocks base method. +func (m *MockMerkleDB) GetProof(ctx context.Context, keyBytes []byte) (*Proof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProof", ctx, keyBytes) + ret0, _ := ret[0].(*Proof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProof indicates an expected call of GetProof. +func (mr *MockMerkleDBMockRecorder) GetProof(ctx, keyBytes any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProof", reflect.TypeOf((*MockMerkleDB)(nil).GetProof), ctx, keyBytes) +} + +// GetRangeProof mocks base method. +func (m *MockMerkleDB) GetRangeProof(ctx context.Context, start, end maybe.Maybe[[]byte], maxLength int) (*RangeProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRangeProof", ctx, start, end, maxLength) + ret0, _ := ret[0].(*RangeProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRangeProof indicates an expected call of GetRangeProof. +func (mr *MockMerkleDBMockRecorder) GetRangeProof(ctx, start, end, maxLength any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRangeProof", reflect.TypeOf((*MockMerkleDB)(nil).GetRangeProof), ctx, start, end, maxLength) +} + +// GetRangeProofAtRoot mocks base method. +func (m *MockMerkleDB) GetRangeProofAtRoot(ctx context.Context, rootID ids.ID, start, end maybe.Maybe[[]byte], maxLength int) (*RangeProof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRangeProofAtRoot", ctx, rootID, start, end, maxLength) + ret0, _ := ret[0].(*RangeProof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRangeProofAtRoot indicates an expected call of GetRangeProofAtRoot. +func (mr *MockMerkleDBMockRecorder) GetRangeProofAtRoot(ctx, rootID, start, end, maxLength any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRangeProofAtRoot", reflect.TypeOf((*MockMerkleDB)(nil).GetRangeProofAtRoot), ctx, rootID, start, end, maxLength) +} + +// GetValue mocks base method. +func (m *MockMerkleDB) GetValue(ctx context.Context, key []byte) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetValue", ctx, key) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetValue indicates an expected call of GetValue. +func (mr *MockMerkleDBMockRecorder) GetValue(ctx, key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValue", reflect.TypeOf((*MockMerkleDB)(nil).GetValue), ctx, key) +} + +// GetValues mocks base method. +func (m *MockMerkleDB) GetValues(ctx context.Context, keys [][]byte) ([][]byte, []error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetValues", ctx, keys) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].([]error) + return ret0, ret1 +} + +// GetValues indicates an expected call of GetValues. +func (mr *MockMerkleDBMockRecorder) GetValues(ctx, keys any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValues", reflect.TypeOf((*MockMerkleDB)(nil).GetValues), ctx, keys) +} + +// Has mocks base method. +func (m *MockMerkleDB) Has(key []byte) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Has", key) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Has indicates an expected call of Has. +func (mr *MockMerkleDBMockRecorder) Has(key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockMerkleDB)(nil).Has), key) +} + +// HealthCheck mocks base method. +func (m *MockMerkleDB) HealthCheck(arg0 context.Context) (any, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HealthCheck", arg0) + ret0, _ := ret[0].(any) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HealthCheck indicates an expected call of HealthCheck. +func (mr *MockMerkleDBMockRecorder) HealthCheck(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockMerkleDB)(nil).HealthCheck), arg0) +} + +// NewBatch mocks base method. +func (m *MockMerkleDB) NewBatch() database.Batch { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewBatch") + ret0, _ := ret[0].(database.Batch) + return ret0 +} + +// NewBatch indicates an expected call of NewBatch. +func (mr *MockMerkleDBMockRecorder) NewBatch() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewBatch", reflect.TypeOf((*MockMerkleDB)(nil).NewBatch)) +} + +// NewIterator mocks base method. +func (m *MockMerkleDB) NewIterator() database.Iterator { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewIterator") + ret0, _ := ret[0].(database.Iterator) + return ret0 +} + +// NewIterator indicates an expected call of NewIterator. +func (mr *MockMerkleDBMockRecorder) NewIterator() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIterator", reflect.TypeOf((*MockMerkleDB)(nil).NewIterator)) +} + +// NewIteratorWithPrefix mocks base method. +func (m *MockMerkleDB) NewIteratorWithPrefix(prefix []byte) database.Iterator { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewIteratorWithPrefix", prefix) + ret0, _ := ret[0].(database.Iterator) + return ret0 +} + +// NewIteratorWithPrefix indicates an expected call of NewIteratorWithPrefix. +func (mr *MockMerkleDBMockRecorder) NewIteratorWithPrefix(prefix any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithPrefix", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithPrefix), prefix) +} + +// NewIteratorWithStart mocks base method. +func (m *MockMerkleDB) NewIteratorWithStart(start []byte) database.Iterator { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewIteratorWithStart", start) + ret0, _ := ret[0].(database.Iterator) + return ret0 +} + +// NewIteratorWithStart indicates an expected call of NewIteratorWithStart. +func (mr *MockMerkleDBMockRecorder) NewIteratorWithStart(start any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithStart", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithStart), start) +} + +// NewIteratorWithStartAndPrefix mocks base method. +func (m *MockMerkleDB) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewIteratorWithStartAndPrefix", start, prefix) + ret0, _ := ret[0].(database.Iterator) + return ret0 +} + +// NewIteratorWithStartAndPrefix indicates an expected call of NewIteratorWithStartAndPrefix. +func (mr *MockMerkleDBMockRecorder) NewIteratorWithStartAndPrefix(start, prefix any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithStartAndPrefix", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithStartAndPrefix), start, prefix) +} + +// NewView mocks base method. +func (m *MockMerkleDB) NewView(ctx context.Context, changes ViewChanges) (View, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewView", ctx, changes) + ret0, _ := ret[0].(View) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NewView indicates an expected call of NewView. +func (mr *MockMerkleDBMockRecorder) NewView(ctx, changes any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewView", reflect.TypeOf((*MockMerkleDB)(nil).NewView), ctx, changes) +} + +// PrefetchPath mocks base method. +func (m *MockMerkleDB) PrefetchPath(key []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PrefetchPath", key) + ret0, _ := ret[0].(error) + return ret0 +} + +// PrefetchPath indicates an expected call of PrefetchPath. +func (mr *MockMerkleDBMockRecorder) PrefetchPath(key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrefetchPath", reflect.TypeOf((*MockMerkleDB)(nil).PrefetchPath), key) +} + +// PrefetchPaths mocks base method. +func (m *MockMerkleDB) PrefetchPaths(keys [][]byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PrefetchPaths", keys) + ret0, _ := ret[0].(error) + return ret0 +} + +// PrefetchPaths indicates an expected call of PrefetchPaths. +func (mr *MockMerkleDBMockRecorder) PrefetchPaths(keys any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrefetchPaths", reflect.TypeOf((*MockMerkleDB)(nil).PrefetchPaths), keys) +} + +// Put mocks base method. +func (m *MockMerkleDB) Put(key, value []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Put", key, value) + ret0, _ := ret[0].(error) + return ret0 +} + +// Put indicates an expected call of Put. +func (mr *MockMerkleDBMockRecorder) Put(key, value any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockMerkleDB)(nil).Put), key, value) +} + +// VerifyChangeProof mocks base method. +func (m *MockMerkleDB) VerifyChangeProof(ctx context.Context, proof *ChangeProof, start, end maybe.Maybe[[]byte], expectedEndRootID ids.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyChangeProof", ctx, proof, start, end, expectedEndRootID) + ret0, _ := ret[0].(error) + return ret0 +} + +// VerifyChangeProof indicates an expected call of VerifyChangeProof. +func (mr *MockMerkleDBMockRecorder) VerifyChangeProof(ctx, proof, start, end, expectedEndRootID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).VerifyChangeProof), ctx, proof, start, end, expectedEndRootID) +} + +// getEditableNode mocks base method. +func (m *MockMerkleDB) getEditableNode(key Key, hasValue bool) (*node, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getEditableNode", key, hasValue) + ret0, _ := ret[0].(*node) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// getEditableNode indicates an expected call of getEditableNode. +func (mr *MockMerkleDBMockRecorder) getEditableNode(key, hasValue any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getEditableNode", reflect.TypeOf((*MockMerkleDB)(nil).getEditableNode), key, hasValue) +} + +// getNode mocks base method. +func (m *MockMerkleDB) getNode(key Key, hasValue bool) (*node, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getNode", key, hasValue) + ret0, _ := ret[0].(*node) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// getNode indicates an expected call of getNode. +func (mr *MockMerkleDBMockRecorder) getNode(key, hasValue any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getNode", reflect.TypeOf((*MockMerkleDB)(nil).getNode), key, hasValue) +} + +// getRoot mocks base method. +func (m *MockMerkleDB) getRoot() maybe.Maybe[*node] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getRoot") + ret0, _ := ret[0].(maybe.Maybe[*node]) + return ret0 +} + +// getRoot indicates an expected call of getRoot. +func (mr *MockMerkleDBMockRecorder) getRoot() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getRoot", reflect.TypeOf((*MockMerkleDB)(nil).getRoot)) +} + +// getTokenSize mocks base method. +func (m *MockMerkleDB) getTokenSize() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getTokenSize") + ret0, _ := ret[0].(int) + return ret0 +} + +// getTokenSize indicates an expected call of getTokenSize. +func (mr *MockMerkleDBMockRecorder) getTokenSize() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getTokenSize", reflect.TypeOf((*MockMerkleDB)(nil).getTokenSize)) +} + +// getValue mocks base method. +func (m *MockMerkleDB) getValue(key Key) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getValue", key) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// getValue indicates an expected call of getValue. +func (mr *MockMerkleDBMockRecorder) getValue(key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getValue", reflect.TypeOf((*MockMerkleDB)(nil).getValue), key) +} From a33e0dfc18152081552384432fa768987cbd4f3e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 15:27:29 -0400 Subject: [PATCH 115/199] Include validationID --- vms/platformvm/state/state.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 77698f07449e..129d210b120c 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2373,10 +2373,12 @@ func (s *state) writeSubnetOnlyValidators(updateValidators bool, height uint64) // Perform additions: for validationID, sov := range sovChanges { + validationID := validationID + subnetIDNodeIDKey := make([]byte, len(sov.SubnetID)+len(sov.NodeID)) copy(subnetIDNodeIDKey, sov.SubnetID[:]) copy(subnetIDNodeIDKey[len(sov.SubnetID):], sov.NodeID[:]) - if err := s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, nil); err != nil { + if err := s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, validationID[:]); err != nil { return err } From cc9f7aeec3db9d797ae58c823abdb7c841003c03 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 15:48:38 -0400 Subject: [PATCH 116/199] write conversionID --- vms/platformvm/txs/convert_subnet_tx.go | 16 +++-------- .../txs/executor/standard_tx_executor.go | 28 +++++++++++++++---- vms/platformvm/txs/fee/complexity.go | 3 +- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/vms/platformvm/txs/convert_subnet_tx.go b/vms/platformvm/txs/convert_subnet_tx.go index b3dc6abb6175..ad446bb6c433 100644 --- a/vms/platformvm/txs/convert_subnet_tx.go +++ b/vms/platformvm/txs/convert_subnet_tx.go @@ -28,7 +28,6 @@ var ( ErrConvertMustIncludeValidators = errors.New("conversion must include at least one validator") ErrConvertValidatorsNotSortedAndUnique = errors.New("conversion validators must be sorted and unique") ErrZeroWeight = errors.New("validator weight must be non-zero") - ErrMissingPublicKey = errors.New("missing public key") ) type ConvertSubnetTx struct { @@ -84,7 +83,7 @@ func (tx *ConvertSubnetTx) Visit(visitor Visitor) error { } type ConvertSubnetValidator struct { - // TODO: Must be Ed25519 NodeID + // TODO: Should be a variable length nodeID NodeID ids.NodeID `serialize:"true" json:"nodeID"` // Weight of this validator used when sampling Weight uint64 `serialize:"true" json:"weight"` @@ -94,7 +93,7 @@ type ConvertSubnetValidator struct { // Note: We do not enforce that the BLS key is unique across all validators. // This means that validators can share a key if they so choose. // However, a NodeID + Subnet does uniquely map to a BLS key - Signer signer.Signer `serialize:"true" json:"signer"` + Signer signer.ProofOfPossession `serialize:"true" json:"signer"` // Leftover $AVAX from the [Balance] will be issued to this owner once it is // removed from the validator set. RemainingBalanceOwner message.PChainOwner `serialize:"true" json:"remainingBalanceOwner"` @@ -110,8 +109,8 @@ func (v *ConvertSubnetValidator) Verify() error { if v.Weight == 0 { return ErrZeroWeight } - err := verify.All( - v.Signer, + return verify.All( + &v.Signer, &secp256k1fx.OutputOwners{ Threshold: v.RemainingBalanceOwner.Threshold, Addrs: v.RemainingBalanceOwner.Addresses, @@ -121,11 +120,4 @@ func (v *ConvertSubnetValidator) Verify() error { Addrs: v.DeactivationOwner.Addresses, }, ) - if err != nil { - return err - } - if v.Signer.Key() == nil { - return ErrMissingPublicKey - } - return nil } diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 5b351b4885a4..99fab2fd8bbc 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -23,6 +23,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" ) var ( @@ -528,9 +529,14 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { } var ( - txID = e.Tx.ID() - startTime = uint64(currentTimestamp.Unix()) - currentFees = e.State.GetAccruedFees() + startTime = uint64(currentTimestamp.Unix()) + currentFees = e.State.GetAccruedFees() + subnetConversionData = message.SubnetConversionData{ + SubnetID: tx.Subnet, + ManagerChainID: tx.ChainID, + ManagerAddress: tx.Address, + Validators: make([]message.SubnetConversionValidatorData, len(tx.Validators)), + } ) for i, vdr := range tx.Validators { vdr := vdr @@ -575,6 +581,12 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { if err := e.State.PutSubnetOnlyValidator(sov); err != nil { return err } + + subnetConversionData.Validators[i] = message.SubnetConversionValidatorData{ + NodeID: vdr.NodeID.Bytes(), + BLSPublicKey: vdr.Signer.PublicKey, + Weight: vdr.Weight, + } } if err := e.Backend.FlowChecker.VerifySpend( tx, @@ -589,13 +601,19 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { return err } + conversionID, err := message.SubnetConversionID(subnetConversionData) + if err != nil { + return err + } + + var txID = e.Tx.ID() + // Consume the UTXOS avax.Consume(e.State, tx.Ins) // Produce the UTXOS avax.Produce(e.State, txID, tx.Outs) // Set the new Subnet manager in the database - // TODO: Populate the conversionID - e.State.SetSubnetConversion(tx.Subnet, ids.Empty, tx.ChainID, tx.Address) + e.State.SetSubnetConversion(tx.Subnet, conversionID, tx.ChainID, tx.Address) return nil } diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index bcdc66bddf3e..1262e49a632a 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -65,7 +65,6 @@ const ( intrinsicConvertSubnetValidatorBandwidth = ids.NodeIDLen + // nodeID wrappers.LongLen + // weight wrappers.LongLen + // balance - wrappers.IntLen + // signer typeID wrappers.IntLen + // remaining balance owner threshold wrappers.IntLen + // remaining balance owner num addresses wrappers.IntLen + // deactivation owner threshold @@ -342,7 +341,7 @@ func convertSubnetValidatorComplexity(sov txs.ConvertSubnetValidator) (gas.Dimen gas.Compute: 0, // TODO: Add compute complexity } - signerComplexity, err := SignerComplexity(sov.Signer) + signerComplexity, err := SignerComplexity(&sov.Signer) if err != nil { return gas.Dimensions{}, err } From 904eeca8560ad9483c3c04f7d17102468d5ed707 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 15:51:25 -0400 Subject: [PATCH 117/199] merged --- vms/platformvm/txs/executor/standard_tx_executor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index d4c0df1e0dfe..e523bcfd3c66 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -860,7 +860,7 @@ func (e *StandardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat return fmt.Errorf("expected nonce to be at least %d but got %d", sov.MinNonce, msg.Nonce) } - expectedChainID, expectedAddress, err := e.State.GetSubnetManager(sov.SubnetID) + _, expectedChainID, expectedAddress, err := e.State.GetSubnetConversion(sov.SubnetID) if err != nil { return err } From c0432585585b5b50ebfadfac819a164f8be56393 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 16:24:04 -0400 Subject: [PATCH 118/199] Add SubnetConversion signing --- vms/platformvm/network/warp.go | 44 +++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/vms/platformvm/network/warp.go b/vms/platformvm/network/warp.go index 3fb9520855df..d07a516a0f16 100644 --- a/vms/platformvm/network/warp.go +++ b/vms/platformvm/network/warp.go @@ -10,6 +10,7 @@ import ( "sync" "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p/acp118" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/vms/platformvm/state" @@ -25,6 +26,9 @@ const ( ErrUnsupportedWarpAddressedCallPayloadType ErrFailedToParseJustification + ErrConversionDoesNotExist + ErrMismatchedConversionID + ErrMismatchedValidationID ErrValidationDoesNotExist ErrValidationExists @@ -33,8 +37,6 @@ const ( ErrImpossibleNonce ErrWrongNonce ErrWrongWeight - - errUnimplemented // TODO: Remove ) var _ acp118.Verifier = (*signatureRequestVerifier)(nil) @@ -90,13 +92,39 @@ func (s signatureRequestVerifier) verifySubnetConversion( msg *message.SubnetConversion, justification []byte, ) *common.AppError { - _ = s - _ = msg - _ = justification - return &common.AppError{ - Code: errUnimplemented, - Message: "unimplemented", + subnetID, err := ids.ToID(justification) + if err != nil { + return &common.AppError{ + Code: ErrFailedToParseJustification, + Message: "failed to parse justification: " + err.Error(), + } + } + + s.stateLock.Lock() + defer s.stateLock.Unlock() + + conversionID, _, _, err := s.state.GetSubnetConversion(subnetID) + if err == database.ErrNotFound { + return &common.AppError{ + Code: ErrConversionDoesNotExist, + Message: fmt.Sprintf("subnet %q has not been converted", subnetID), + } + } + if err != nil { + return &common.AppError{ + Code: common.ErrUndefined.Code, + Message: "failed to get subnet conversionID: " + err.Error(), + } + } + + if msg.ID != conversionID { + return &common.AppError{ + Code: ErrMismatchedConversionID, + Message: fmt.Sprintf("provided conversionID %q != expected conversionID %q", msg.ID, conversionID), + } } + + return nil } func (s signatureRequestVerifier) verifySubnetValidatorRegistration( From f13cf7a1b0ef1919267c95a6d7376585718a9d5a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 18:07:43 -0400 Subject: [PATCH 119/199] Update validationID --- ids/id.go | 16 ++++++++++++++++ .../txs/executor/standard_tx_executor.go | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ids/id.go b/ids/id.go index 91eacfdbd08f..4be8e7ecb1e8 100644 --- a/ids/id.go +++ b/ids/id.go @@ -107,6 +107,22 @@ func (id ID) Prefix(prefixes ...uint64) ID { return hashing.ComputeHash256Array(packer.Bytes) } +// Append this id to create a more selective id. +// +// This is used to generate the ACP-77 validationIDs. +func (id ID) Append(suffixes ...uint32) ID { + packer := wrappers.Packer{ + Bytes: make([]byte, IDLen+len(suffixes)*wrappers.IntLen), + } + + packer.PackFixedBytes(id[:]) + for _, suffix := range suffixes { + packer.PackInt(suffix) + } + + return hashing.ComputeHash256Array(packer.Bytes) +} + // XOR this id and the provided id and return the resulting id. // // Note: this id is not modified. diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 99fab2fd8bbc..a268dbd2f168 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -550,7 +550,7 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { } sov := state.SubnetOnlyValidator{ - ValidationID: tx.Subnet.Prefix(uint64(i)), // TODO: The spec says this should be a postfix, not a preifx + ValidationID: tx.Subnet.Append(uint32(i)), SubnetID: tx.Subnet, NodeID: vdr.NodeID, PublicKey: bls.PublicKeyToUncompressedBytes(vdr.Signer.Key()), From c80e103ca2ef7957c464b03dbc112cd9fd24410c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 18:16:26 -0400 Subject: [PATCH 120/199] nit --- vms/platformvm/txs/executor/standard_tx_executor.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index dd4c5ef1bba4..0406d474a7bb 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -16,7 +16,6 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -725,7 +724,7 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return err } - validationID := hashing.ComputeHash256Array(addressedCall.Payload) + validationID := msg.ValidationID() expiry := state.ExpiryEntry{ Timestamp: msg.Expiry, ValidationID: validationID, From 5cbe8b65b313244af26710711193b69e920611d2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 18:27:05 -0400 Subject: [PATCH 121/199] reduce diff --- vms/platformvm/warp/message/payload.go | 2 +- vms/platformvm/warp/message/register_subnet_validator.go | 2 +- vms/platformvm/warp/message/subnet_conversion.go | 2 +- vms/platformvm/warp/message/subnet_validator_registration.go | 2 +- vms/platformvm/warp/message/subnet_validator_weight.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/warp/message/payload.go b/vms/platformvm/warp/message/payload.go index 1da0f71aa9b5..6903cc22001d 100644 --- a/vms/platformvm/warp/message/payload.go +++ b/vms/platformvm/warp/message/payload.go @@ -43,7 +43,7 @@ func Parse(bytes []byte) (Payload, error) { return p, nil } -func Initialize(p Payload) error { +func initialize(p Payload) error { bytes, err := Codec.Marshal(CodecVersion, &p) if err != nil { return fmt.Errorf("couldn't marshal %T payload: %w", p, err) diff --git a/vms/platformvm/warp/message/register_subnet_validator.go b/vms/platformvm/warp/message/register_subnet_validator.go index ba4eb3aab168..cf0b1cbcd569 100644 --- a/vms/platformvm/warp/message/register_subnet_validator.go +++ b/vms/platformvm/warp/message/register_subnet_validator.go @@ -99,7 +99,7 @@ func NewRegisterSubnetValidator( DisableOwner: disableOwner, Weight: weight, } - return msg, Initialize(msg) + return msg, initialize(msg) } // ParseRegisterSubnetValidator parses bytes into an initialized diff --git a/vms/platformvm/warp/message/subnet_conversion.go b/vms/platformvm/warp/message/subnet_conversion.go index f1af8e78e5ab..a93354fbe446 100644 --- a/vms/platformvm/warp/message/subnet_conversion.go +++ b/vms/platformvm/warp/message/subnet_conversion.go @@ -50,7 +50,7 @@ func NewSubnetConversion(id ids.ID) (*SubnetConversion, error) { msg := &SubnetConversion{ ID: id, } - return msg, Initialize(msg) + return msg, initialize(msg) } // ParseSubnetConversion parses bytes into an initialized SubnetConversion. diff --git a/vms/platformvm/warp/message/subnet_validator_registration.go b/vms/platformvm/warp/message/subnet_validator_registration.go index f0e1919b1e24..2ab46c88dc3b 100644 --- a/vms/platformvm/warp/message/subnet_validator_registration.go +++ b/vms/platformvm/warp/message/subnet_validator_registration.go @@ -34,7 +34,7 @@ func NewSubnetValidatorRegistration( ValidationID: validationID, Registered: registered, } - return msg, Initialize(msg) + return msg, initialize(msg) } // ParseSubnetValidatorRegistration parses bytes into an initialized diff --git a/vms/platformvm/warp/message/subnet_validator_weight.go b/vms/platformvm/warp/message/subnet_validator_weight.go index 5b94889a8f03..dcfa6c5a16c0 100644 --- a/vms/platformvm/warp/message/subnet_validator_weight.go +++ b/vms/platformvm/warp/message/subnet_validator_weight.go @@ -46,7 +46,7 @@ func NewSubnetValidatorWeight( Nonce: nonce, Weight: weight, } - return msg, Initialize(msg) + return msg, initialize(msg) } // ParseSubnetValidatorWeight parses bytes into an initialized From 3985f77204548a6afb5e68ed22a6a207c91f95e3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 18:29:07 -0400 Subject: [PATCH 122/199] add comment --- vms/platformvm/txs/executor/standard_tx_executor.go | 1 + 1 file changed, 1 insertion(+) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 9fbab2387f0b..9fca4301759e 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -958,6 +958,7 @@ func (e *StandardTxExecutor) IncreaseBalanceTx(tx *txs.IncreaseBalanceTx) error return err } + // If the validator is currently inactive, we are activating it. if sov.EndAccumulatedFee == 0 { sov.EndAccumulatedFee = e.State.GetAccruedFees() } From 646ce06f5be18e2c990bf370362af831fae581a4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 18:39:41 -0400 Subject: [PATCH 123/199] update nodeID to variable length --- vms/platformvm/txs/convert_subnet_tx.go | 14 ++++-- vms/platformvm/txs/convert_subnet_tx_test.go | 50 +++++++++----------- vms/platformvm/txs/fee/complexity.go | 3 +- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/vms/platformvm/txs/convert_subnet_tx.go b/vms/platformvm/txs/convert_subnet_tx.go index ad446bb6c433..c6d8791a5a60 100644 --- a/vms/platformvm/txs/convert_subnet_tx.go +++ b/vms/platformvm/txs/convert_subnet_tx.go @@ -4,6 +4,7 @@ package txs import ( + "bytes" "errors" "github.com/ava-labs/avalanchego/ids" @@ -83,8 +84,8 @@ func (tx *ConvertSubnetTx) Visit(visitor Visitor) error { } type ConvertSubnetValidator struct { - // TODO: Should be a variable length nodeID - NodeID ids.NodeID `serialize:"true" json:"nodeID"` + // NodeID of this validator + NodeID types.JSONByteSlice `serialize:"true" json:"nodeID"` // Weight of this validator used when sampling Weight uint64 `serialize:"true" json:"weight"` // Initial balance for this validator @@ -102,13 +103,20 @@ type ConvertSubnetValidator struct { } func (v ConvertSubnetValidator) Compare(o ConvertSubnetValidator) int { - return v.NodeID.Compare(o.NodeID) + return bytes.Compare(v.NodeID, o.NodeID) } func (v *ConvertSubnetValidator) Verify() error { if v.Weight == 0 { return ErrZeroWeight } + nodeID, err := ids.ToNodeID(v.NodeID) + if err != nil { + return err + } + if nodeID == ids.EmptyNodeID { + return errEmptyNodeID + } return verify.All( &v.Signer, &secp256k1fx.OutputOwners{ diff --git a/vms/platformvm/txs/convert_subnet_tx_test.go b/vms/platformvm/txs/convert_subnet_tx_test.go index c1b212702914..e3b5752142f2 100644 --- a/vms/platformvm/txs/convert_subnet_tx_test.go +++ b/vms/platformvm/txs/convert_subnet_tx_test.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/snowtest" + "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/units" @@ -296,10 +297,10 @@ func TestConvertSubnetTxSerialization(t *testing.T) { Address: managerAddress, Validators: []ConvertSubnetValidator{ { - NodeID: nodeID, + NodeID: nodeID[:], Weight: 0x0102030405060708, Balance: units.Avax, - Signer: signer.NewProofOfPossession(sk), + Signer: *signer.NewProofOfPossession(sk), RemainingBalanceOwner: message.PChainOwner{ Threshold: 1, Addresses: []ids.ShortID{ @@ -559,10 +560,10 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { invalidAddress = make(types.JSONByteSlice, MaxSubnetAddressLength+1) validValidators = []ConvertSubnetValidator{ { - NodeID: ids.GenerateTestNodeID(), + NodeID: utils.RandomBytes(ids.NodeIDLen), Weight: 1, Balance: 1, - Signer: signer.NewProofOfPossession(sk), + Signer: *signer.NewProofOfPossession(sk), RemainingBalanceOwner: message.PChainOwner{}, DeactivationOwner: message.PChainOwner{}, }, @@ -636,10 +637,18 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { Subnet: validSubnetID, Validators: []ConvertSubnetValidator{ { - NodeID: ids.NodeID{1}, + NodeID: []byte{ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }, }, { - NodeID: ids.NodeID{0}, + NodeID: []byte{ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }, }, }, SubnetAuth: validSubnetAuth, @@ -653,8 +662,9 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { Subnet: validSubnetID, Validators: []ConvertSubnetValidator{ { + NodeID: utils.RandomBytes(ids.NodeIDLen), Weight: 0, - Signer: signer.NewProofOfPossession(sk), + Signer: *signer.NewProofOfPossession(sk), RemainingBalanceOwner: message.PChainOwner{}, DeactivationOwner: message.PChainOwner{}, }, @@ -670,8 +680,9 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { Subnet: validSubnetID, Validators: []ConvertSubnetValidator{ { + NodeID: utils.RandomBytes(ids.NodeIDLen), Weight: 1, - Signer: &signer.ProofOfPossession{}, + Signer: signer.ProofOfPossession{}, RemainingBalanceOwner: message.PChainOwner{}, DeactivationOwner: message.PChainOwner{}, }, @@ -687,8 +698,9 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { Subnet: validSubnetID, Validators: []ConvertSubnetValidator{ { + NodeID: utils.RandomBytes(ids.NodeIDLen), Weight: 1, - Signer: signer.NewProofOfPossession(sk), + Signer: *signer.NewProofOfPossession(sk), RemainingBalanceOwner: message.PChainOwner{ Threshold: 1, }, @@ -706,8 +718,9 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { Subnet: validSubnetID, Validators: []ConvertSubnetValidator{ { + NodeID: utils.RandomBytes(ids.NodeIDLen), Weight: 1, - Signer: signer.NewProofOfPossession(sk), + Signer: *signer.NewProofOfPossession(sk), RemainingBalanceOwner: message.PChainOwner{}, DeactivationOwner: message.PChainOwner{ Threshold: 1, @@ -718,23 +731,6 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { }, expectedErr: secp256k1fx.ErrOutputUnspendable, }, - { - name: "invalid validator signer", - tx: &ConvertSubnetTx{ - BaseTx: validBaseTx, - Subnet: validSubnetID, - Validators: []ConvertSubnetValidator{ - { - Weight: 1, - Signer: &signer.Empty{}, - RemainingBalanceOwner: message.PChainOwner{}, - DeactivationOwner: message.PChainOwner{}, - }, - }, - SubnetAuth: validSubnetAuth, - }, - expectedErr: ErrMissingPublicKey, - }, { name: "invalid BaseTx", tx: &ConvertSubnetTx{ diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 1262e49a632a..139834d2276e 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -62,7 +62,8 @@ const ( intrinsicSECP256k1FxSignatureBandwidth = wrappers.IntLen + // signature index secp256k1.SignatureLen // signature length - intrinsicConvertSubnetValidatorBandwidth = ids.NodeIDLen + // nodeID + intrinsicConvertSubnetValidatorBandwidth = wrappers.IntLen + // nodeID length + ids.NodeIDLen + // nodeID wrappers.LongLen + // weight wrappers.LongLen + // balance wrappers.IntLen + // remaining balance owner threshold From c757fac40acb5aa5101a94b47ab8f0887c335fb1 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 18:46:07 -0400 Subject: [PATCH 124/199] fix executor --- vms/platformvm/txs/executor/standard_tx_executor.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index a268dbd2f168..7e74d2459e08 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -540,6 +540,12 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { ) for i, vdr := range tx.Validators { vdr := vdr + + nodeID, err := ids.ToNodeID(vdr.NodeID) + if err != nil { + return err + } + remainingBalanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &vdr.RemainingBalanceOwner) if err != nil { return err @@ -552,7 +558,7 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { sov := state.SubnetOnlyValidator{ ValidationID: tx.Subnet.Append(uint32(i)), SubnetID: tx.Subnet, - NodeID: vdr.NodeID, + NodeID: nodeID, PublicKey: bls.PublicKeyToUncompressedBytes(vdr.Signer.Key()), RemainingBalanceOwner: remainingBalanceOwner, DeactivationOwner: deactivationOwner, @@ -583,7 +589,7 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { } subnetConversionData.Validators[i] = message.SubnetConversionValidatorData{ - NodeID: vdr.NodeID.Bytes(), + NodeID: vdr.NodeID, BLSPublicKey: vdr.Signer.PublicKey, Weight: vdr.Weight, } From e64230d4c77e47168ee93ac34f5fed0cce5b0f7a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 3 Oct 2024 12:29:13 -0400 Subject: [PATCH 125/199] Fix verification --- vms/platformvm/txs/convert_subnet_tx.go | 8 +-- vms/platformvm/txs/fee/complexity.go | 4 +- wallet/chain/p/builder/builder.go | 4 +- .../chain/p/builder/builder_with_options.go | 2 +- wallet/chain/p/wallet/wallet.go | 4 +- wallet/chain/p/wallet/with_options.go | 2 +- .../primary/examples/convert-subnet/main.go | 54 ++++++++++++++----- 7 files changed, 53 insertions(+), 25 deletions(-) diff --git a/vms/platformvm/txs/convert_subnet_tx.go b/vms/platformvm/txs/convert_subnet_tx.go index c6d8791a5a60..7f76c962a207 100644 --- a/vms/platformvm/txs/convert_subnet_tx.go +++ b/vms/platformvm/txs/convert_subnet_tx.go @@ -21,8 +21,8 @@ import ( const MaxSubnetAddressLength = 4096 var ( - _ UnsignedTx = (*ConvertSubnetTx)(nil) - _ utils.Sortable[ConvertSubnetValidator] = ConvertSubnetValidator{} + _ UnsignedTx = (*ConvertSubnetTx)(nil) + _ utils.Sortable[*ConvertSubnetValidator] = (*ConvertSubnetValidator)(nil) ErrConvertPermissionlessSubnet = errors.New("cannot convert a permissionless subnet") ErrAddressTooLong = errors.New("address is too long") @@ -41,7 +41,7 @@ type ConvertSubnetTx struct { // Address of the Subnet manager Address types.JSONByteSlice `serialize:"true" json:"address"` // Initial pay-as-you-go validators for the Subnet - Validators []ConvertSubnetValidator `serialize:"true" json:"validators"` + Validators []*ConvertSubnetValidator `serialize:"true" json:"validators"` // Authorizes this conversion SubnetAuth verify.Verifiable `serialize:"true" json:"subnetAuthorization"` } @@ -102,7 +102,7 @@ type ConvertSubnetValidator struct { DeactivationOwner message.PChainOwner `serialize:"true" json:"deactivationOwner"` } -func (v ConvertSubnetValidator) Compare(o ConvertSubnetValidator) int { +func (v *ConvertSubnetValidator) Compare(o *ConvertSubnetValidator) int { return bytes.Compare(v.NodeID, o.NodeID) } diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 139834d2276e..7f7de85efe4d 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -318,7 +318,7 @@ func inputComplexity(in *avax.TransferableInput) (gas.Dimensions, error) { // ConvertSubnetValidatorComplexity returns the complexity the validators add to // a transaction. -func ConvertSubnetValidatorComplexity(sovs ...txs.ConvertSubnetValidator) (gas.Dimensions, error) { +func ConvertSubnetValidatorComplexity(sovs ...*txs.ConvertSubnetValidator) (gas.Dimensions, error) { var complexity gas.Dimensions for _, sov := range sovs { sovComplexity, err := convertSubnetValidatorComplexity(sov) @@ -334,7 +334,7 @@ func ConvertSubnetValidatorComplexity(sovs ...txs.ConvertSubnetValidator) (gas.D return complexity, nil } -func convertSubnetValidatorComplexity(sov txs.ConvertSubnetValidator) (gas.Dimensions, error) { +func convertSubnetValidatorComplexity(sov *txs.ConvertSubnetValidator) (gas.Dimensions, error) { complexity := gas.Dimensions{ gas.Bandwidth: intrinsicConvertSubnetValidatorBandwidth, gas.DBRead: 0, diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index 5c9c7d3d08d1..d4c2b75e827d 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -160,7 +160,7 @@ type Builder interface { subnetID ids.ID, chainID ids.ID, address []byte, - validators []txs.ConvertSubnetValidator, + validators []*txs.ConvertSubnetValidator, options ...common.Option, ) (*txs.ConvertSubnetTx, error) @@ -784,7 +784,7 @@ func (b *builder) NewConvertSubnetTx( subnetID ids.ID, chainID ids.ID, address []byte, - validators []txs.ConvertSubnetValidator, + validators []*txs.ConvertSubnetValidator, options ...common.Option, ) (*txs.ConvertSubnetTx, error) { var ( diff --git a/wallet/chain/p/builder/builder_with_options.go b/wallet/chain/p/builder/builder_with_options.go index 6201dde1fa8f..8432944e3542 100644 --- a/wallet/chain/p/builder/builder_with_options.go +++ b/wallet/chain/p/builder/builder_with_options.go @@ -159,7 +159,7 @@ func (b *builderWithOptions) NewConvertSubnetTx( subnetID ids.ID, chainID ids.ID, address []byte, - validators []txs.ConvertSubnetValidator, + validators []*txs.ConvertSubnetValidator, options ...common.Option, ) (*txs.ConvertSubnetTx, error) { return b.builder.NewConvertSubnetTx( diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index 2f1d788fd7d5..1e0c520b5eca 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -146,7 +146,7 @@ type Wallet interface { subnetID ids.ID, chainID ids.ID, address []byte, - validators []txs.ConvertSubnetValidator, + validators []*txs.ConvertSubnetValidator, options ...common.Option, ) (*txs.Tx, error) @@ -394,7 +394,7 @@ func (w *wallet) IssueConvertSubnetTx( subnetID ids.ID, chainID ids.ID, address []byte, - validators []txs.ConvertSubnetValidator, + validators []*txs.ConvertSubnetValidator, options ...common.Option, ) (*txs.Tx, error) { utx, err := w.builder.NewConvertSubnetTx(subnetID, chainID, address, validators, options...) diff --git a/wallet/chain/p/wallet/with_options.go b/wallet/chain/p/wallet/with_options.go index 3027b46d5428..ae6b4b4d8fa9 100644 --- a/wallet/chain/p/wallet/with_options.go +++ b/wallet/chain/p/wallet/with_options.go @@ -147,7 +147,7 @@ func (w *withOptions) IssueConvertSubnetTx( subnetID ids.ID, chainID ids.ID, address []byte, - validators []txs.ConvertSubnetValidator, + validators []*txs.ConvertSubnetValidator, options ...common.Option, ) (*txs.Tx, error) { return w.wallet.IssueConvertSubnetTx( diff --git a/wallet/subnet/primary/examples/convert-subnet/main.go b/wallet/subnet/primary/examples/convert-subnet/main.go index c4233f80cefe..b7133488f6ef 100644 --- a/wallet/subnet/primary/examples/convert-subnet/main.go +++ b/wallet/subnet/primary/examples/convert-subnet/main.go @@ -5,6 +5,7 @@ package main import ( "context" + "encoding/hex" "log" "time" @@ -13,6 +14,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/wallet/subnet/primary" ) @@ -21,12 +23,14 @@ func main() { key := genesis.EWOQKey uri := "http://localhost:9700" kc := secp256k1fx.NewKeychain(key) - subnetIDStr := "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof" + subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") + chainID := ids.FromStringOrPanic("E8nTR9TtRwfkS7XFjTYUYHENQ91mkPMtDUwwCeu7rNgBBtkqu") + addressHex := "" weight := units.Schmeckle - subnetID, err := ids.FromString(subnetIDStr) + address, err := hex.DecodeString(addressHex) if err != nil { - log.Fatalf("failed to parse subnet ID: %s\n", err) + log.Fatalf("failed to decode address %q: %s\n", addressHex, err) } ctx := context.Background() @@ -39,6 +43,23 @@ func main() { } log.Printf("fetched node ID %s in %s\n", nodeID, time.Since(nodeInfoStartTime)) + validationID := subnetID.Append(0) + conversionID, err := message.SubnetConversionID(message.SubnetConversionData{ + SubnetID: subnetID, + ManagerChainID: chainID, + ManagerAddress: address, + Validators: []message.SubnetConversionValidatorData{ + { + NodeID: nodeID.Bytes(), + BLSPublicKey: nodePoP.PublicKey, + Weight: weight, + }, + }, + }) + if err != nil { + log.Fatalf("failed to calculate conversionID: %s\n", err) + } + // MakeWallet fetches the available UTXOs owned by [kc] on the network that // [uri] is hosting and registers [subnetID]. walletSyncStartTime := time.Now() @@ -57,22 +78,29 @@ func main() { pWallet := wallet.P() convertSubnetStartTime := time.Now() - addValidatorTx, err := pWallet.IssueConvertSubnetTx( + convertSubnetTx, err := pWallet.IssueConvertSubnetTx( subnetID, - ids.Empty, - nil, - []txs.ConvertSubnetValidator{ + chainID, + address, + []*txs.ConvertSubnetValidator{ { - NodeID: nodeID, + NodeID: nodeID.Bytes(), Weight: weight, - Balance: 30 * units.NanoAvax, // 30s before being deactivated - Signer: nodePoP, - RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + Balance: units.Avax, + Signer: *nodePoP, + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{}, }, }, ) if err != nil { - log.Fatalf("failed to issue add subnet validator transaction: %s\n", err) + log.Fatalf("failed to issue subnet conversion transaction: %s\n", err) } - log.Printf("added new subnet validator %s to %s with %s in %s\n", nodeID, subnetID, addValidatorTx.ID(), time.Since(convertSubnetStartTime)) + log.Printf("converted subnet %s with transactionID %s, validationID %s, and conversionID %s in %s\n", + subnetID, + convertSubnetTx.ID(), + validationID, + conversionID, + time.Since(convertSubnetStartTime), + ) } From 4a38f38d5f2b1cb6c71525a0c382017af8f796f6 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 3 Oct 2024 12:32:55 -0400 Subject: [PATCH 126/199] fix merge --- wallet/subnet/primary/examples/convert-subnet/main.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/wallet/subnet/primary/examples/convert-subnet/main.go b/wallet/subnet/primary/examples/convert-subnet/main.go index 1c13769b4f72..b7133488f6ef 100644 --- a/wallet/subnet/primary/examples/convert-subnet/main.go +++ b/wallet/subnet/primary/examples/convert-subnet/main.go @@ -23,13 +23,8 @@ func main() { key := genesis.EWOQKey uri := "http://localhost:9700" kc := secp256k1fx.NewKeychain(key) -<<<<<<< HEAD - subnetID := ids.FromStringOrPanic("2eZYSgCU738xN7aRw47NsBUPqnKkoqJMYUJexTsX19VdTNSZc9") - chainID := ids.FromStringOrPanic("2ko3NCPzHeneKWcYfy55pgAgU1LV9Q9XNrNv2sWG4W2XzE3ViV") -======= subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") chainID := ids.FromStringOrPanic("E8nTR9TtRwfkS7XFjTYUYHENQ91mkPMtDUwwCeu7rNgBBtkqu") ->>>>>>> implement-acp-77-register-subnet-validator-tx addressHex := "" weight := units.Schmeckle From 2b624ab1e8d44b49ffcb21637ac2214aeafd681f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 3 Oct 2024 13:41:32 -0400 Subject: [PATCH 127/199] Fix disableSubnetValidatorTx --- vms/platformvm/block/executor/manager.go | 17 +- vms/platformvm/network/gossip.go | 2 +- vms/platformvm/txs/codec.go | 1 + .../txs/disable_subnet_validator_tx.go | 2 +- .../txs/executor/standard_tx_executor.go | 2 +- vms/platformvm/warp/message/payload.go | 2 +- .../warp/message/register_subnet_validator.go | 2 +- .../warp/message/subnet_conversion.go | 2 +- .../message/subnet_validator_registration.go | 2 +- .../warp/message/subnet_validator_weight.go | 2 +- .../primary/examples/convert-subnet/main.go | 2 +- .../examples/disable-subnet-validator/main.go | 54 +++++ .../primary/examples/increase-balance/main.go | 56 +++++ .../register-subnet-validator/main.go | 20 +- .../set-subnet-validator-weight/main.go | 42 +++- .../examples/sign-subnet-conversion/main.go | 119 +++++++++++ .../main.go | 106 +++++----- .../sign-subnet-validator-removal/main.go | 196 ++++++++++++++++++ .../sign-subnet-validator-weight/main.go | 127 ++++++++++++ wallet/subnet/primary/wallet.go | 6 +- 20 files changed, 680 insertions(+), 82 deletions(-) create mode 100644 wallet/subnet/primary/examples/disable-subnet-validator/main.go create mode 100644 wallet/subnet/primary/examples/increase-balance/main.go create mode 100644 wallet/subnet/primary/examples/sign-subnet-conversion/main.go create mode 100644 wallet/subnet/primary/examples/sign-subnet-validator-removal/main.go create mode 100644 wallet/subnet/primary/examples/sign-subnet-validator-weight/main.go diff --git a/vms/platformvm/block/executor/manager.go b/vms/platformvm/block/executor/manager.go index 2954671a5b89..a607926c9135 100644 --- a/vms/platformvm/block/executor/manager.go +++ b/vms/platformvm/block/executor/manager.go @@ -6,6 +6,7 @@ package executor import ( "context" "errors" + "fmt" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/consensus/snowman" @@ -125,7 +126,7 @@ func (m *manager) VerifyTx(tx *txs.Tx) error { recommendedPChainHeight, err := m.ctx.ValidatorState.GetMinimumHeight(context.TODO()) if err != nil { - return err + return fmt.Errorf("failed to fetch P-chain height: %w", err) } err = executor.VerifyWarpMessages( context.TODO(), @@ -135,12 +136,12 @@ func (m *manager) VerifyTx(tx *txs.Tx) error { tx.Unsigned, ) if err != nil { - return err + return fmt.Errorf("failed verifying warp messages: %w", err) } stateDiff, err := state.NewDiff(m.preferred, m) if err != nil { - return err + return fmt.Errorf("failed creating state diff: %w", err) } nextBlkTime, _, err := state.NextBlockTime( @@ -149,21 +150,25 @@ func (m *manager) VerifyTx(tx *txs.Tx) error { m.txExecutorBackend.Clk, ) if err != nil { - return err + return fmt.Errorf("failed selecting next block time: %w", err) } _, err = executor.AdvanceTimeTo(m.txExecutorBackend, stateDiff, nextBlkTime) if err != nil { - return err + return fmt.Errorf("failed to advance the chain time: %w", err) } feeCalculator := state.PickFeeCalculator(m.txExecutorBackend.Config, stateDiff) - return tx.Unsigned.Visit(&executor.StandardTxExecutor{ + err = tx.Unsigned.Visit(&executor.StandardTxExecutor{ Backend: m.txExecutorBackend, State: stateDiff, FeeCalculator: feeCalculator, Tx: tx, }) + if err != nil { + return fmt.Errorf("failed execution: %w", err) + } + return nil } func (m *manager) VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error { diff --git a/vms/platformvm/network/gossip.go b/vms/platformvm/network/gossip.go index 7e6e7adc341c..43e730b90e31 100644 --- a/vms/platformvm/network/gossip.go +++ b/vms/platformvm/network/gossip.go @@ -109,7 +109,7 @@ func (g *gossipMempool) Add(tx *txs.Tx) error { if err := g.txVerifier.VerifyTx(tx); err != nil { g.Mempool.MarkDropped(txID, err) - return err + return fmt.Errorf("failed verification: %w", err) } if err := g.Mempool.Add(tx); err != nil { diff --git a/vms/platformvm/txs/codec.go b/vms/platformvm/txs/codec.go index dbe84b196a1a..e3b4539e91e2 100644 --- a/vms/platformvm/txs/codec.go +++ b/vms/platformvm/txs/codec.go @@ -126,5 +126,6 @@ func RegisterEtnaTypes(targetCodec linearcodec.Codec) error { targetCodec.RegisterType(&RegisterSubnetValidatorTx{}), targetCodec.RegisterType(&SetSubnetValidatorWeightTx{}), targetCodec.RegisterType(&IncreaseBalanceTx{}), + targetCodec.RegisterType(&DisableSubnetValidatorTx{}), ) } diff --git a/vms/platformvm/txs/disable_subnet_validator_tx.go b/vms/platformvm/txs/disable_subnet_validator_tx.go index ca03f4208644..08e59ed1fa56 100644 --- a/vms/platformvm/txs/disable_subnet_validator_tx.go +++ b/vms/platformvm/txs/disable_subnet_validator_tx.go @@ -15,7 +15,7 @@ type DisableSubnetValidatorTx struct { // Metadata, inputs and outputs BaseTx `serialize:"true"` // ID corresponding to the validator - ValidationID ids.ID `json:"validationID"` + ValidationID ids.ID `serialize:"true" json:"validationID"` // Authorizes this validator to be disabled DisableAuth verify.Verifiable `serialize:"true" json:"disableAuthorization"` } diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index f01e493730b0..243f9f925976 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -1004,7 +1004,7 @@ func (e *StandardTxExecutor) DisableSubnetValidatorTx(tx *txs.DisableSubnetValid sov, err := e.State.GetSubnetOnlyValidator(tx.ValidationID) if err != nil { - return err + return fmt.Errorf("couldn't load SoV for %s: %w", tx.ValidationID, err) } var disableOwner message.PChainOwner diff --git a/vms/platformvm/warp/message/payload.go b/vms/platformvm/warp/message/payload.go index 6903cc22001d..1da0f71aa9b5 100644 --- a/vms/platformvm/warp/message/payload.go +++ b/vms/platformvm/warp/message/payload.go @@ -43,7 +43,7 @@ func Parse(bytes []byte) (Payload, error) { return p, nil } -func initialize(p Payload) error { +func Initialize(p Payload) error { bytes, err := Codec.Marshal(CodecVersion, &p) if err != nil { return fmt.Errorf("couldn't marshal %T payload: %w", p, err) diff --git a/vms/platformvm/warp/message/register_subnet_validator.go b/vms/platformvm/warp/message/register_subnet_validator.go index cf0b1cbcd569..ba4eb3aab168 100644 --- a/vms/platformvm/warp/message/register_subnet_validator.go +++ b/vms/platformvm/warp/message/register_subnet_validator.go @@ -99,7 +99,7 @@ func NewRegisterSubnetValidator( DisableOwner: disableOwner, Weight: weight, } - return msg, initialize(msg) + return msg, Initialize(msg) } // ParseRegisterSubnetValidator parses bytes into an initialized diff --git a/vms/platformvm/warp/message/subnet_conversion.go b/vms/platformvm/warp/message/subnet_conversion.go index a93354fbe446..f1af8e78e5ab 100644 --- a/vms/platformvm/warp/message/subnet_conversion.go +++ b/vms/platformvm/warp/message/subnet_conversion.go @@ -50,7 +50,7 @@ func NewSubnetConversion(id ids.ID) (*SubnetConversion, error) { msg := &SubnetConversion{ ID: id, } - return msg, initialize(msg) + return msg, Initialize(msg) } // ParseSubnetConversion parses bytes into an initialized SubnetConversion. diff --git a/vms/platformvm/warp/message/subnet_validator_registration.go b/vms/platformvm/warp/message/subnet_validator_registration.go index 2ab46c88dc3b..f0e1919b1e24 100644 --- a/vms/platformvm/warp/message/subnet_validator_registration.go +++ b/vms/platformvm/warp/message/subnet_validator_registration.go @@ -34,7 +34,7 @@ func NewSubnetValidatorRegistration( ValidationID: validationID, Registered: registered, } - return msg, initialize(msg) + return msg, Initialize(msg) } // ParseSubnetValidatorRegistration parses bytes into an initialized diff --git a/vms/platformvm/warp/message/subnet_validator_weight.go b/vms/platformvm/warp/message/subnet_validator_weight.go index dcfa6c5a16c0..5b94889a8f03 100644 --- a/vms/platformvm/warp/message/subnet_validator_weight.go +++ b/vms/platformvm/warp/message/subnet_validator_weight.go @@ -46,7 +46,7 @@ func NewSubnetValidatorWeight( Nonce: nonce, Weight: weight, } - return msg, initialize(msg) + return msg, Initialize(msg) } // ParseSubnetValidatorWeight parses bytes into an initialized diff --git a/wallet/subnet/primary/examples/convert-subnet/main.go b/wallet/subnet/primary/examples/convert-subnet/main.go index b7133488f6ef..f78e0cd92b39 100644 --- a/wallet/subnet/primary/examples/convert-subnet/main.go +++ b/wallet/subnet/primary/examples/convert-subnet/main.go @@ -24,7 +24,7 @@ func main() { uri := "http://localhost:9700" kc := secp256k1fx.NewKeychain(key) subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") - chainID := ids.FromStringOrPanic("E8nTR9TtRwfkS7XFjTYUYHENQ91mkPMtDUwwCeu7rNgBBtkqu") + chainID := ids.FromStringOrPanic("2BMFrJ9xeh5JdwZEx6uuFcjfZC2SV2hdbMT8ee5HrvjtfJb5br") addressHex := "" weight := units.Schmeckle diff --git a/wallet/subnet/primary/examples/disable-subnet-validator/main.go b/wallet/subnet/primary/examples/disable-subnet-validator/main.go new file mode 100644 index 000000000000..cb2030ef3307 --- /dev/null +++ b/wallet/subnet/primary/examples/disable-subnet-validator/main.go @@ -0,0 +1,54 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "log" + "time" + + "github.com/ava-labs/avalanchego/genesis" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" +) + +func main() { + key := genesis.EWOQKey + uri := primary.LocalAPIURI + kc := secp256k1fx.NewKeychain(key) + validationID := ids.FromStringOrPanic("9FAftNgNBrzHUMMApsSyV6RcFiL9UmCbvsCu28xdLV2mQ7CMo") + + ctx := context.Background() + + // MakeWallet fetches the available UTXOs owned by [kc] on the network that + // [uri] is hosting and registers [subnetID]. + walletSyncStartTime := time.Now() + wallet, err := primary.MakeWallet(ctx, &primary.WalletConfig{ + URI: uri, + AVAXKeychain: kc, + EthKeychain: kc, + ValidationIDs: []ids.ID{validationID}, + }) + if err != nil { + log.Fatalf("failed to initialize wallet: %s\n", err) + } + log.Printf("synced wallet in %s\n", time.Since(walletSyncStartTime)) + + // Get the P-chain wallet + pWallet := wallet.P() + + disableSubnetValidatorStartTime := time.Now() + disableSubnetValidatorTx, err := pWallet.IssueDisableSubnetValidatorTx( + validationID, + ) + if err != nil { + log.Fatalf("failed to issue disable subnet validator transaction: %s\n", err) + } + log.Printf("disabled %s with %s in %s\n", + validationID, + disableSubnetValidatorTx.ID(), + time.Since(disableSubnetValidatorStartTime), + ) +} diff --git a/wallet/subnet/primary/examples/increase-balance/main.go b/wallet/subnet/primary/examples/increase-balance/main.go new file mode 100644 index 000000000000..e0a1af0c1093 --- /dev/null +++ b/wallet/subnet/primary/examples/increase-balance/main.go @@ -0,0 +1,56 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "log" + "time" + + "github.com/ava-labs/avalanchego/genesis" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" +) + +func main() { + key := genesis.EWOQKey + uri := primary.LocalAPIURI + kc := secp256k1fx.NewKeychain(key) + validationID := ids.FromStringOrPanic("9FAftNgNBrzHUMMApsSyV6RcFiL9UmCbvsCu28xdLV2mQ7CMo") + balance := uint64(2) + + ctx := context.Background() + + // MakeWallet fetches the available UTXOs owned by [kc] on the network that + // [uri] is hosting and registers [subnetID]. + walletSyncStartTime := time.Now() + wallet, err := primary.MakeWallet(ctx, &primary.WalletConfig{ + URI: uri, + AVAXKeychain: kc, + EthKeychain: kc, + }) + if err != nil { + log.Fatalf("failed to initialize wallet: %s\n", err) + } + log.Printf("synced wallet in %s\n", time.Since(walletSyncStartTime)) + + // Get the P-chain wallet + pWallet := wallet.P() + + increaseBalanceStartTime := time.Now() + increaseBalanceTx, err := pWallet.IssueIncreaseBalanceTx( + validationID, + balance, + ) + if err != nil { + log.Fatalf("failed to issue increase balance transaction: %s\n", err) + } + log.Printf("increased balance of validationID %s by %d with %s in %s\n", + validationID, + balance, + increaseBalanceTx.ID(), + time.Since(increaseBalanceStartTime), + ) +} diff --git a/wallet/subnet/primary/examples/register-subnet-validator/main.go b/wallet/subnet/primary/examples/register-subnet-validator/main.go index 41c7f1661a9c..05d51aa1719b 100644 --- a/wallet/subnet/primary/examples/register-subnet-validator/main.go +++ b/wallet/subnet/primary/examples/register-subnet-validator/main.go @@ -15,7 +15,6 @@ import ( "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/platformvm/warp" @@ -30,9 +29,9 @@ func main() { uri := "http://localhost:9710" kc := secp256k1fx.NewKeychain(key) subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") - chainID := ids.FromStringOrPanic("21G9Uqg2R7hYa81kZK7oMCgstsHEiNGbkpB9kdBLUeWx3wWMsV") + chainID := ids.FromStringOrPanic("2BMFrJ9xeh5JdwZEx6uuFcjfZC2SV2hdbMT8ee5HrvjtfJb5br") addressHex := "" - weight := units.Schmeckle + weight := uint64(1) address, err := hex.DecodeString(addressHex) if err != nil { @@ -79,9 +78,11 @@ func main() { addressedCallPayload, err := message.NewRegisterSubnetValidator( subnetID, nodeID, - weight, nodePoP.PublicKey, uint64(time.Now().Add(5*time.Minute).Unix()), + message.PChainOwner{}, + message.PChainOwner{}, + weight, ) if err != nil { log.Fatalf("failed to create RegisterSubnetValidator message: %s\n", err) @@ -128,17 +129,16 @@ func main() { log.Fatalf("failed to create Warp message: %s\n", err) } - convertSubnetStartTime := time.Now() - addValidatorTx, err := pWallet.IssueRegisterSubnetValidatorTx( + registerSubnetValidatorStartTime := time.Now() + registerSubnetValidatorTx, err := pWallet.IssueRegisterSubnetValidatorTx( units.Avax, nodePoP.ProofOfPossession, - &secp256k1fx.OutputOwners{}, warp.Bytes(), ) if err != nil { - log.Fatalf("failed to issue add subnet validator transaction: %s\n", err) + log.Fatalf("failed to issue register subnet validator transaction: %s\n", err) } - var validationID ids.ID = hashing.ComputeHash256Array(addressedCallPayload.Bytes()) - log.Printf("added new subnet validator %s to subnet %s with txID %s as validationID %s in %s\n", nodeID, subnetID, addValidatorTx.ID(), validationID, time.Since(convertSubnetStartTime)) + validationID := addressedCallPayload.ValidationID() + log.Printf("registered new subnet validator %s to subnet %s with txID %s as validationID %s in %s\n", nodeID, subnetID, registerSubnetValidatorTx.ID(), validationID, time.Since(registerSubnetValidatorStartTime)) } diff --git a/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go b/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go index 082268505a65..35e7ce264104 100644 --- a/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go +++ b/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go @@ -6,11 +6,15 @@ package main import ( "context" "encoding/hex" + "encoding/json" "log" + "os" "time" "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -22,17 +26,27 @@ func main() { key := genesis.EWOQKey uri := primary.LocalAPIURI kc := secp256k1fx.NewKeychain(key) - chainID := ids.FromStringOrPanic("2ko3NCPzHeneKWcYfy55pgAgU1LV9Q9XNrNv2sWG4W2XzE3ViV") + chainID := ids.FromStringOrPanic("2BMFrJ9xeh5JdwZEx6uuFcjfZC2SV2hdbMT8ee5HrvjtfJb5br") addressHex := "" - validationID := ids.FromStringOrPanic("225kHLzuaBd6rhxZ8aq91kmgLJyPTFtTFVAWJDaPyKRdDiTpQo") + validationID := ids.FromStringOrPanic("2Y3ZZZXxpzm46geqVuqFXeSFVbeKihgrfeXRDaiF4ds6R2N8M5") nonce := uint64(1) - weight := uint64(0) + weight := uint64(2) address, err := hex.DecodeString(addressHex) if err != nil { log.Fatalf("failed to decode address %q: %s\n", addressHex, err) } + skBytes, err := os.ReadFile("/Users/stephen/.avalanchego/staking/signer.key") + if err != nil { + log.Fatalf("failed to read signer key: %s\n", err) + } + + sk, err := bls.SecretKeyFromBytes(skBytes) + if err != nil { + log.Fatalf("failed to parse secret key: %s\n", err) + } + // MakeWallet fetches the available UTXOs owned by [kc] on the network that // [uri] is hosting and registers [subnetID]. walletSyncStartTime := time.Now() @@ -51,14 +65,19 @@ func main() { pWallet := wallet.P() context := pWallet.Builder().Context() - addressedCallPayload, err := message.NewSetSubnetValidatorWeight( + addressedCallPayload, err := message.NewSubnetValidatorWeight( validationID, nonce, weight, ) if err != nil { - log.Fatalf("failed to create SetSubnetValidatorWeight message: %s\n", err) + log.Fatalf("failed to create SubnetValidatorWeight message: %s\n", err) + } + addressedCallPayloadJSON, err := json.MarshalIndent(addressedCallPayload, "", "\t") + if err != nil { + log.Fatalf("failed to marshal SubnetValidatorWeight message: %s\n", err) } + log.Println(string(addressedCallPayloadJSON)) addressedCall, err := payload.NewAddressedCall( address, @@ -77,9 +96,20 @@ func main() { log.Fatalf("failed to create unsigned Warp message: %s\n", err) } + signers := set.NewBits() + signers.Add(0) // [signers] has weight from [vdr[0]] + + unsignedBytes := unsignedWarp.Bytes() + sig := bls.Sign(sk, unsignedBytes) + sigBytes := [bls.SignatureLen]byte{} + copy(sigBytes[:], bls.SignatureToBytes(sig)) + warp, err := warp.NewMessage( unsignedWarp, - &warp.BitSetSignature{}, + &warp.BitSetSignature{ + Signers: signers.Bytes(), + Signature: sigBytes, + }, ) if err != nil { log.Fatalf("failed to create Warp message: %s\n", err) diff --git a/wallet/subnet/primary/examples/sign-subnet-conversion/main.go b/wallet/subnet/primary/examples/sign-subnet-conversion/main.go new file mode 100644 index 000000000000..87f159855853 --- /dev/null +++ b/wallet/subnet/primary/examples/sign-subnet-conversion/main.go @@ -0,0 +1,119 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "log" + "net/netip" + "time" + + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/protobuf/proto" + + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/peer" + "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/snow/networking/router" + "github.com/ava-labs/avalanchego/utils/compression" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + + p2pmessage "github.com/ava-labs/avalanchego/message" + warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" +) + +func main() { + uri := primary.LocalAPIURI + subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") + conversionID := ids.FromStringOrPanic("d84X4xQeXkAhnLQi2BqDyG6AEGbdJVGJCwx6a4c3UnBhD9vpZ") + infoClient := info.NewClient(uri) + networkID, err := infoClient.GetNetworkID(context.Background()) + if err != nil { + log.Fatalf("failed to fetch network ID: %s\n", err) + } + + subnetConversion, err := warpmessage.NewSubnetConversion(conversionID) + if err != nil { + log.Fatalf("failed to create SubnetConversion message: %s\n", err) + } + + addressedCall, err := payload.NewAddressedCall( + nil, + subnetConversion.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create AddressedCall message: %s\n", err) + } + + unsignedWarp, err := warp.NewUnsignedMessage( + networkID, + constants.PlatformChainID, + addressedCall.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create unsigned Warp message: %s\n", err) + } + + p, err := peer.StartTestPeer( + context.Background(), + netip.AddrPortFrom( + netip.AddrFrom4([4]byte{127, 0, 0, 1}), + 9651, + ), + networkID, + router.InboundHandlerFunc(func(_ context.Context, msg p2pmessage.InboundMessage) { + log.Printf("received %s: %s", msg.Op(), msg.Message()) + }), + ) + if err != nil { + log.Fatalf("failed to start peer: %s\n", err) + } + + mesageBuilder, err := p2pmessage.NewCreator( + logging.NoLog{}, + prometheus.NewRegistry(), + compression.TypeZstd, + time.Hour, + ) + if err != nil { + log.Fatalf("failed to create message builder: %s\n", err) + } + + appRequestPayload, err := proto.Marshal(&sdk.SignatureRequest{ + Message: unsignedWarp.Bytes(), + Justification: subnetID[:], + }) + if err != nil { + log.Fatalf("failed to marshal SignatureRequest: %s\n", err) + } + + appRequest, err := mesageBuilder.AppRequest( + constants.PlatformChainID, + 0, + time.Hour, + p2p.PrefixMessage( + p2p.ProtocolPrefix(p2p.SignatureRequestHandlerID), + appRequestPayload, + ), + ) + if err != nil { + log.Fatalf("failed to create AppRequest: %s\n", err) + } + + p.Send(context.Background(), appRequest) + + time.Sleep(5 * time.Second) + + p.StartClose() + err = p.AwaitClosed(context.Background()) + if err != nil { + log.Fatalf("failed to close peer: %s\n", err) + } +} diff --git a/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go b/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go index 8ba1bc80a098..6506d2477c2c 100644 --- a/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go +++ b/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go @@ -14,14 +14,12 @@ import ( "google.golang.org/protobuf/proto" "github.com/ava-labs/avalanchego/api/info" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/network/peer" "github.com/ava-labs/avalanchego/proto/pb/sdk" "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/utils/compression" "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -33,59 +31,67 @@ import ( var registerSubnetValidatorJSON = []byte(`{ "subnetID": "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof", - "nodeID": "NodeID-9Nm8NNALws11M5J89rXgc46u6L52e7fmz", - "weight": 49463, + "nodeID": "0xb628ee3952a5de80fadd31ab030a67189edb1410", "blsPublicKey": [ + 143, + 167, + 255, + 128, + 221, + 92, + 126, + 190, + 134, + 189, + 157, + 166, + 6, + 55, + 92, + 125, + 223, + 231, + 71, + 85, + 122, + 110, + 110, + 49, + 215, + 14, + 1, + 226, + 146, + 140, + 73, + 75, + 113, + 163, 138, - 25, - 41, - 204, - 242, - 137, - 56, - 208, - 15, - 227, - 216, - 136, + 158, + 34, + 207, + 99, + 36, 137, - 144, - 59, - 120, - 205, - 181, - 230, - 232, - 225, + 55, + 191, + 28, + 186, 24, - 80, - 117, - 225, - 16, - 102, - 93, - 83, - 184, - 245, - 240, - 232, - 97, - 238, 49, - 217, - 4, - 131, - 121, - 19, - 213, - 202, - 233, - 38, - 19, - 70, - 58 + 199 ], - "expiry": 1727548306 + "expiry": 1727975059, + "remainingBalanceOwner": { + "threshold": 0, + "addresses": null + }, + "disableOwner": { + "threshold": 0, + "addresses": null + }, + "weight": 1 }`) func main() { @@ -106,7 +112,7 @@ func main() { log.Fatalf("failed to initialize RegisterSubnetValidator message: %s\n", err) } - var validationID ids.ID = hashing.ComputeHash256Array(registerSubnetValidator.Bytes()) + validationID := registerSubnetValidator.ValidationID() subnetValidatorRegistration, err := warpmessage.NewSubnetValidatorRegistration( validationID, true, diff --git a/wallet/subnet/primary/examples/sign-subnet-validator-removal/main.go b/wallet/subnet/primary/examples/sign-subnet-validator-removal/main.go new file mode 100644 index 000000000000..02ae39abef7d --- /dev/null +++ b/wallet/subnet/primary/examples/sign-subnet-validator-removal/main.go @@ -0,0 +1,196 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "encoding/json" + "log" + "net/netip" + "time" + + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/protobuf/proto" + + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/peer" + "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/snow/networking/router" + "github.com/ava-labs/avalanchego/utils/compression" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + + p2pmessage "github.com/ava-labs/avalanchego/message" + warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" +) + +var registerSubnetValidatorJSON = []byte(`{ + "subnetID": "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof", + "nodeID": "0xb628ee3952a5de80fadd31ab030a67189edb1410", + "blsPublicKey": [ + 143, + 167, + 255, + 128, + 221, + 92, + 126, + 190, + 134, + 189, + 157, + 166, + 6, + 55, + 92, + 125, + 223, + 231, + 71, + 85, + 122, + 110, + 110, + 49, + 215, + 14, + 1, + 226, + 146, + 140, + 73, + 75, + 113, + 163, + 138, + 158, + 34, + 207, + 99, + 36, + 137, + 55, + 191, + 28, + 186, + 24, + 49, + 199 + ], + "expiry": 1727975059, + "remainingBalanceOwner": { + "threshold": 0, + "addresses": null + }, + "disableOwner": { + "threshold": 0, + "addresses": null + }, + "weight": 1 +}`) + +func main() { + uri := primary.LocalAPIURI + infoClient := info.NewClient(uri) + networkID, err := infoClient.GetNetworkID(context.Background()) + if err != nil { + log.Fatalf("failed to fetch network ID: %s\n", err) + } + + var registerSubnetValidator warpmessage.RegisterSubnetValidator + err = json.Unmarshal(registerSubnetValidatorJSON, ®isterSubnetValidator) + if err != nil { + log.Fatalf("failed to unmarshal RegisterSubnetValidator message: %s\n", err) + } + err = warpmessage.Initialize(®isterSubnetValidator) + if err != nil { + log.Fatalf("failed to initialize RegisterSubnetValidator message: %s\n", err) + } + + validationID := registerSubnetValidator.ValidationID() + subnetValidatorRegistration, err := warpmessage.NewSubnetValidatorRegistration( + validationID, + false, + ) + if err != nil { + log.Fatalf("failed to create SubnetValidatorRegistration message: %s\n", err) + } + + addressedCall, err := payload.NewAddressedCall( + nil, + subnetValidatorRegistration.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create AddressedCall message: %s\n", err) + } + + unsignedWarp, err := warp.NewUnsignedMessage( + networkID, + constants.PlatformChainID, + addressedCall.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create unsigned Warp message: %s\n", err) + } + + p, err := peer.StartTestPeer( + context.Background(), + netip.AddrPortFrom( + netip.AddrFrom4([4]byte{127, 0, 0, 1}), + 9651, + ), + networkID, + router.InboundHandlerFunc(func(_ context.Context, msg p2pmessage.InboundMessage) { + log.Printf("received %s: %s", msg.Op(), msg.Message()) + }), + ) + if err != nil { + log.Fatalf("failed to start peer: %s\n", err) + } + + mesageBuilder, err := p2pmessage.NewCreator( + logging.NoLog{}, + prometheus.NewRegistry(), + compression.TypeZstd, + time.Hour, + ) + if err != nil { + log.Fatalf("failed to create message builder: %s\n", err) + } + + appRequestPayload, err := proto.Marshal(&sdk.SignatureRequest{ + Message: unsignedWarp.Bytes(), + Justification: registerSubnetValidator.Bytes(), + }) + if err != nil { + log.Fatalf("failed to marshal SignatureRequest: %s\n", err) + } + + appRequest, err := mesageBuilder.AppRequest( + constants.PlatformChainID, + 0, + time.Hour, + p2p.PrefixMessage( + p2p.ProtocolPrefix(p2p.SignatureRequestHandlerID), + appRequestPayload, + ), + ) + if err != nil { + log.Fatalf("failed to create AppRequest: %s\n", err) + } + + p.Send(context.Background(), appRequest) + + time.Sleep(5 * time.Second) + + p.StartClose() + err = p.AwaitClosed(context.Background()) + if err != nil { + log.Fatalf("failed to close peer: %s\n", err) + } +} diff --git a/wallet/subnet/primary/examples/sign-subnet-validator-weight/main.go b/wallet/subnet/primary/examples/sign-subnet-validator-weight/main.go new file mode 100644 index 000000000000..26a8dd1cfcb3 --- /dev/null +++ b/wallet/subnet/primary/examples/sign-subnet-validator-weight/main.go @@ -0,0 +1,127 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "encoding/json" + "log" + "net/netip" + "time" + + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/protobuf/proto" + + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/peer" + "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/snow/networking/router" + "github.com/ava-labs/avalanchego/utils/compression" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + + p2pmessage "github.com/ava-labs/avalanchego/message" + warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" +) + +var subnetValidatorWeightJSON = []byte(`{ + "validationID": "2Y3ZZZXxpzm46geqVuqFXeSFVbeKihgrfeXRDaiF4ds6R2N8M5", + "nonce": 1, + "weight": 2 +}`) + +func main() { + uri := primary.LocalAPIURI + infoClient := info.NewClient(uri) + networkID, err := infoClient.GetNetworkID(context.Background()) + if err != nil { + log.Fatalf("failed to fetch network ID: %s\n", err) + } + + var subnetValidatorWeight warpmessage.SubnetValidatorWeight + err = json.Unmarshal(subnetValidatorWeightJSON, &subnetValidatorWeight) + if err != nil { + log.Fatalf("failed to unmarshal SubnetValidatorWeight message: %s\n", err) + } + err = warpmessage.Initialize(&subnetValidatorWeight) + if err != nil { + log.Fatalf("failed to initialize SubnetValidatorWeight message: %s\n", err) + } + + addressedCall, err := payload.NewAddressedCall( + nil, + subnetValidatorWeight.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create AddressedCall message: %s\n", err) + } + + unsignedWarp, err := warp.NewUnsignedMessage( + networkID, + constants.PlatformChainID, + addressedCall.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create unsigned Warp message: %s\n", err) + } + + p, err := peer.StartTestPeer( + context.Background(), + netip.AddrPortFrom( + netip.AddrFrom4([4]byte{127, 0, 0, 1}), + 9651, + ), + networkID, + router.InboundHandlerFunc(func(_ context.Context, msg p2pmessage.InboundMessage) { + log.Printf("received %s: %s", msg.Op(), msg.Message()) + }), + ) + if err != nil { + log.Fatalf("failed to start peer: %s\n", err) + } + + mesageBuilder, err := p2pmessage.NewCreator( + logging.NoLog{}, + prometheus.NewRegistry(), + compression.TypeZstd, + time.Hour, + ) + if err != nil { + log.Fatalf("failed to create message builder: %s\n", err) + } + + appRequestPayload, err := proto.Marshal(&sdk.SignatureRequest{ + Message: unsignedWarp.Bytes(), + }) + if err != nil { + log.Fatalf("failed to marshal SignatureRequest: %s\n", err) + } + + appRequest, err := mesageBuilder.AppRequest( + constants.PlatformChainID, + 0, + time.Hour, + p2p.PrefixMessage( + p2p.ProtocolPrefix(p2p.SignatureRequestHandlerID), + appRequestPayload, + ), + ) + if err != nil { + log.Fatalf("failed to create AppRequest: %s\n", err) + } + + p.Send(context.Background(), appRequest) + + time.Sleep(5 * time.Second) + + p.StartClose() + err = p.AwaitClosed(context.Background()) + if err != nil { + log.Fatalf("failed to close peer: %s\n", err) + } +} diff --git a/wallet/subnet/primary/wallet.go b/wallet/subnet/primary/wallet.go index 751cd03a3049..cb0cab2be56c 100644 --- a/wallet/subnet/primary/wallet.go +++ b/wallet/subnet/primary/wallet.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/vms/platformvm" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/wallet/chain/c" "github.com/ava-labs/avalanchego/wallet/chain/p" "github.com/ava-labs/avalanchego/wallet/chain/x" @@ -108,7 +109,10 @@ func MakeWallet(ctx context.Context, config *WalletConfig) (Wallet, error) { if err != nil { return nil, err } - // TODO: Fetch validation disableOwners + for _, validationID := range config.ValidationIDs { + // TODO: Fetch validation disableOwners + subnetOwners[validationID] = &secp256k1fx.OutputOwners{} + } pUTXOs := common.NewChainUTXOs(constants.PlatformChainID, avaxState.UTXOs) pBackend := pwallet.NewBackend(avaxState.PCTX, pUTXOs, subnetOwners) From 01c9072aafc84e22efeb0ba0ff7f7da9d8b280cd Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 3 Oct 2024 16:17:00 -0400 Subject: [PATCH 128/199] remove unexpected file --- x/merkledb/mock_db.go | 491 ------------------------------------------ 1 file changed, 491 deletions(-) delete mode 100644 x/merkledb/mock_db.go diff --git a/x/merkledb/mock_db.go b/x/merkledb/mock_db.go deleted file mode 100644 index c3bf69cf22f6..000000000000 --- a/x/merkledb/mock_db.go +++ /dev/null @@ -1,491 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: x/merkledb/db.go -// -// Generated by this command: -// -// mockgen -source=x/merkledb/db.go -destination=x/merkledb/mock_db.go -package=merkledb -exclude_interfaces=ChangeProofer,RangeProofer,Clearer,Prefetcher -mock_names=MockMerkleDB=MockMerkleDB -// - -// Package merkledb is a generated GoMock package. -package merkledb - -import ( - context "context" - reflect "reflect" - - database "github.com/ava-labs/avalanchego/database" - ids "github.com/ava-labs/avalanchego/ids" - maybe "github.com/ava-labs/avalanchego/utils/maybe" - gomock "go.uber.org/mock/gomock" -) - -// MockMerkleDB is a mock of MerkleDB interface. -type MockMerkleDB struct { - ctrl *gomock.Controller - recorder *MockMerkleDBMockRecorder -} - -// MockMerkleDBMockRecorder is the mock recorder for MockMerkleDB. -type MockMerkleDBMockRecorder struct { - mock *MockMerkleDB -} - -// NewMockMerkleDB creates a new mock instance. -func NewMockMerkleDB(ctrl *gomock.Controller) *MockMerkleDB { - mock := &MockMerkleDB{ctrl: ctrl} - mock.recorder = &MockMerkleDBMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockMerkleDB) EXPECT() *MockMerkleDBMockRecorder { - return m.recorder -} - -// Clear mocks base method. -func (m *MockMerkleDB) Clear() error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Clear") - ret0, _ := ret[0].(error) - return ret0 -} - -// Clear indicates an expected call of Clear. -func (mr *MockMerkleDBMockRecorder) Clear() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockMerkleDB)(nil).Clear)) -} - -// Close mocks base method. -func (m *MockMerkleDB) Close() error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Close") - ret0, _ := ret[0].(error) - return ret0 -} - -// Close indicates an expected call of Close. -func (mr *MockMerkleDBMockRecorder) Close() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockMerkleDB)(nil).Close)) -} - -// CommitChangeProof mocks base method. -func (m *MockMerkleDB) CommitChangeProof(ctx context.Context, proof *ChangeProof) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CommitChangeProof", ctx, proof) - ret0, _ := ret[0].(error) - return ret0 -} - -// CommitChangeProof indicates an expected call of CommitChangeProof. -func (mr *MockMerkleDBMockRecorder) CommitChangeProof(ctx, proof any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).CommitChangeProof), ctx, proof) -} - -// CommitRangeProof mocks base method. -func (m *MockMerkleDB) CommitRangeProof(ctx context.Context, start, end maybe.Maybe[[]byte], proof *RangeProof) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CommitRangeProof", ctx, start, end, proof) - ret0, _ := ret[0].(error) - return ret0 -} - -// CommitRangeProof indicates an expected call of CommitRangeProof. -func (mr *MockMerkleDBMockRecorder) CommitRangeProof(ctx, start, end, proof any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitRangeProof", reflect.TypeOf((*MockMerkleDB)(nil).CommitRangeProof), ctx, start, end, proof) -} - -// Compact mocks base method. -func (m *MockMerkleDB) Compact(start, limit []byte) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Compact", start, limit) - ret0, _ := ret[0].(error) - return ret0 -} - -// Compact indicates an expected call of Compact. -func (mr *MockMerkleDBMockRecorder) Compact(start, limit any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Compact", reflect.TypeOf((*MockMerkleDB)(nil).Compact), start, limit) -} - -// Delete mocks base method. -func (m *MockMerkleDB) Delete(key []byte) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Delete", key) - ret0, _ := ret[0].(error) - return ret0 -} - -// Delete indicates an expected call of Delete. -func (mr *MockMerkleDBMockRecorder) Delete(key any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockMerkleDB)(nil).Delete), key) -} - -// Get mocks base method. -func (m *MockMerkleDB) Get(key []byte) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Get", key) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Get indicates an expected call of Get. -func (mr *MockMerkleDBMockRecorder) Get(key any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockMerkleDB)(nil).Get), key) -} - -// GetChangeProof mocks base method. -func (m *MockMerkleDB) GetChangeProof(ctx context.Context, startRootID, endRootID ids.ID, start, end maybe.Maybe[[]byte], maxLength int) (*ChangeProof, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetChangeProof", ctx, startRootID, endRootID, start, end, maxLength) - ret0, _ := ret[0].(*ChangeProof) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetChangeProof indicates an expected call of GetChangeProof. -func (mr *MockMerkleDBMockRecorder) GetChangeProof(ctx, startRootID, endRootID, start, end, maxLength any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).GetChangeProof), ctx, startRootID, endRootID, start, end, maxLength) -} - -// GetMerkleRoot mocks base method. -func (m *MockMerkleDB) GetMerkleRoot(ctx context.Context) (ids.ID, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetMerkleRoot", ctx) - ret0, _ := ret[0].(ids.ID) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetMerkleRoot indicates an expected call of GetMerkleRoot. -func (mr *MockMerkleDBMockRecorder) GetMerkleRoot(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMerkleRoot", reflect.TypeOf((*MockMerkleDB)(nil).GetMerkleRoot), ctx) -} - -// GetProof mocks base method. -func (m *MockMerkleDB) GetProof(ctx context.Context, keyBytes []byte) (*Proof, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetProof", ctx, keyBytes) - ret0, _ := ret[0].(*Proof) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetProof indicates an expected call of GetProof. -func (mr *MockMerkleDBMockRecorder) GetProof(ctx, keyBytes any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProof", reflect.TypeOf((*MockMerkleDB)(nil).GetProof), ctx, keyBytes) -} - -// GetRangeProof mocks base method. -func (m *MockMerkleDB) GetRangeProof(ctx context.Context, start, end maybe.Maybe[[]byte], maxLength int) (*RangeProof, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRangeProof", ctx, start, end, maxLength) - ret0, _ := ret[0].(*RangeProof) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetRangeProof indicates an expected call of GetRangeProof. -func (mr *MockMerkleDBMockRecorder) GetRangeProof(ctx, start, end, maxLength any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRangeProof", reflect.TypeOf((*MockMerkleDB)(nil).GetRangeProof), ctx, start, end, maxLength) -} - -// GetRangeProofAtRoot mocks base method. -func (m *MockMerkleDB) GetRangeProofAtRoot(ctx context.Context, rootID ids.ID, start, end maybe.Maybe[[]byte], maxLength int) (*RangeProof, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRangeProofAtRoot", ctx, rootID, start, end, maxLength) - ret0, _ := ret[0].(*RangeProof) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetRangeProofAtRoot indicates an expected call of GetRangeProofAtRoot. -func (mr *MockMerkleDBMockRecorder) GetRangeProofAtRoot(ctx, rootID, start, end, maxLength any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRangeProofAtRoot", reflect.TypeOf((*MockMerkleDB)(nil).GetRangeProofAtRoot), ctx, rootID, start, end, maxLength) -} - -// GetValue mocks base method. -func (m *MockMerkleDB) GetValue(ctx context.Context, key []byte) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetValue", ctx, key) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetValue indicates an expected call of GetValue. -func (mr *MockMerkleDBMockRecorder) GetValue(ctx, key any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValue", reflect.TypeOf((*MockMerkleDB)(nil).GetValue), ctx, key) -} - -// GetValues mocks base method. -func (m *MockMerkleDB) GetValues(ctx context.Context, keys [][]byte) ([][]byte, []error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetValues", ctx, keys) - ret0, _ := ret[0].([][]byte) - ret1, _ := ret[1].([]error) - return ret0, ret1 -} - -// GetValues indicates an expected call of GetValues. -func (mr *MockMerkleDBMockRecorder) GetValues(ctx, keys any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValues", reflect.TypeOf((*MockMerkleDB)(nil).GetValues), ctx, keys) -} - -// Has mocks base method. -func (m *MockMerkleDB) Has(key []byte) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Has", key) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Has indicates an expected call of Has. -func (mr *MockMerkleDBMockRecorder) Has(key any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockMerkleDB)(nil).Has), key) -} - -// HealthCheck mocks base method. -func (m *MockMerkleDB) HealthCheck(arg0 context.Context) (any, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HealthCheck", arg0) - ret0, _ := ret[0].(any) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// HealthCheck indicates an expected call of HealthCheck. -func (mr *MockMerkleDBMockRecorder) HealthCheck(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockMerkleDB)(nil).HealthCheck), arg0) -} - -// NewBatch mocks base method. -func (m *MockMerkleDB) NewBatch() database.Batch { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewBatch") - ret0, _ := ret[0].(database.Batch) - return ret0 -} - -// NewBatch indicates an expected call of NewBatch. -func (mr *MockMerkleDBMockRecorder) NewBatch() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewBatch", reflect.TypeOf((*MockMerkleDB)(nil).NewBatch)) -} - -// NewIterator mocks base method. -func (m *MockMerkleDB) NewIterator() database.Iterator { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewIterator") - ret0, _ := ret[0].(database.Iterator) - return ret0 -} - -// NewIterator indicates an expected call of NewIterator. -func (mr *MockMerkleDBMockRecorder) NewIterator() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIterator", reflect.TypeOf((*MockMerkleDB)(nil).NewIterator)) -} - -// NewIteratorWithPrefix mocks base method. -func (m *MockMerkleDB) NewIteratorWithPrefix(prefix []byte) database.Iterator { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewIteratorWithPrefix", prefix) - ret0, _ := ret[0].(database.Iterator) - return ret0 -} - -// NewIteratorWithPrefix indicates an expected call of NewIteratorWithPrefix. -func (mr *MockMerkleDBMockRecorder) NewIteratorWithPrefix(prefix any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithPrefix", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithPrefix), prefix) -} - -// NewIteratorWithStart mocks base method. -func (m *MockMerkleDB) NewIteratorWithStart(start []byte) database.Iterator { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewIteratorWithStart", start) - ret0, _ := ret[0].(database.Iterator) - return ret0 -} - -// NewIteratorWithStart indicates an expected call of NewIteratorWithStart. -func (mr *MockMerkleDBMockRecorder) NewIteratorWithStart(start any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithStart", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithStart), start) -} - -// NewIteratorWithStartAndPrefix mocks base method. -func (m *MockMerkleDB) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewIteratorWithStartAndPrefix", start, prefix) - ret0, _ := ret[0].(database.Iterator) - return ret0 -} - -// NewIteratorWithStartAndPrefix indicates an expected call of NewIteratorWithStartAndPrefix. -func (mr *MockMerkleDBMockRecorder) NewIteratorWithStartAndPrefix(start, prefix any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithStartAndPrefix", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithStartAndPrefix), start, prefix) -} - -// NewView mocks base method. -func (m *MockMerkleDB) NewView(ctx context.Context, changes ViewChanges) (View, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewView", ctx, changes) - ret0, _ := ret[0].(View) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NewView indicates an expected call of NewView. -func (mr *MockMerkleDBMockRecorder) NewView(ctx, changes any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewView", reflect.TypeOf((*MockMerkleDB)(nil).NewView), ctx, changes) -} - -// PrefetchPath mocks base method. -func (m *MockMerkleDB) PrefetchPath(key []byte) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PrefetchPath", key) - ret0, _ := ret[0].(error) - return ret0 -} - -// PrefetchPath indicates an expected call of PrefetchPath. -func (mr *MockMerkleDBMockRecorder) PrefetchPath(key any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrefetchPath", reflect.TypeOf((*MockMerkleDB)(nil).PrefetchPath), key) -} - -// PrefetchPaths mocks base method. -func (m *MockMerkleDB) PrefetchPaths(keys [][]byte) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PrefetchPaths", keys) - ret0, _ := ret[0].(error) - return ret0 -} - -// PrefetchPaths indicates an expected call of PrefetchPaths. -func (mr *MockMerkleDBMockRecorder) PrefetchPaths(keys any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrefetchPaths", reflect.TypeOf((*MockMerkleDB)(nil).PrefetchPaths), keys) -} - -// Put mocks base method. -func (m *MockMerkleDB) Put(key, value []byte) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Put", key, value) - ret0, _ := ret[0].(error) - return ret0 -} - -// Put indicates an expected call of Put. -func (mr *MockMerkleDBMockRecorder) Put(key, value any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockMerkleDB)(nil).Put), key, value) -} - -// VerifyChangeProof mocks base method. -func (m *MockMerkleDB) VerifyChangeProof(ctx context.Context, proof *ChangeProof, start, end maybe.Maybe[[]byte], expectedEndRootID ids.ID) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "VerifyChangeProof", ctx, proof, start, end, expectedEndRootID) - ret0, _ := ret[0].(error) - return ret0 -} - -// VerifyChangeProof indicates an expected call of VerifyChangeProof. -func (mr *MockMerkleDBMockRecorder) VerifyChangeProof(ctx, proof, start, end, expectedEndRootID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).VerifyChangeProof), ctx, proof, start, end, expectedEndRootID) -} - -// getEditableNode mocks base method. -func (m *MockMerkleDB) getEditableNode(key Key, hasValue bool) (*node, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "getEditableNode", key, hasValue) - ret0, _ := ret[0].(*node) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// getEditableNode indicates an expected call of getEditableNode. -func (mr *MockMerkleDBMockRecorder) getEditableNode(key, hasValue any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getEditableNode", reflect.TypeOf((*MockMerkleDB)(nil).getEditableNode), key, hasValue) -} - -// getNode mocks base method. -func (m *MockMerkleDB) getNode(key Key, hasValue bool) (*node, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "getNode", key, hasValue) - ret0, _ := ret[0].(*node) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// getNode indicates an expected call of getNode. -func (mr *MockMerkleDBMockRecorder) getNode(key, hasValue any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getNode", reflect.TypeOf((*MockMerkleDB)(nil).getNode), key, hasValue) -} - -// getRoot mocks base method. -func (m *MockMerkleDB) getRoot() maybe.Maybe[*node] { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "getRoot") - ret0, _ := ret[0].(maybe.Maybe[*node]) - return ret0 -} - -// getRoot indicates an expected call of getRoot. -func (mr *MockMerkleDBMockRecorder) getRoot() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getRoot", reflect.TypeOf((*MockMerkleDB)(nil).getRoot)) -} - -// getTokenSize mocks base method. -func (m *MockMerkleDB) getTokenSize() int { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "getTokenSize") - ret0, _ := ret[0].(int) - return ret0 -} - -// getTokenSize indicates an expected call of getTokenSize. -func (mr *MockMerkleDBMockRecorder) getTokenSize() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getTokenSize", reflect.TypeOf((*MockMerkleDB)(nil).getTokenSize)) -} - -// getValue mocks base method. -func (m *MockMerkleDB) getValue(key Key) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "getValue", key) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// getValue indicates an expected call of getValue. -func (mr *MockMerkleDBMockRecorder) getValue(key any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getValue", reflect.TypeOf((*MockMerkleDB)(nil).getValue), key) -} From 0893cea5af8588e9706250ad34314333e6233058 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 3 Oct 2024 16:38:46 -0400 Subject: [PATCH 129/199] Lint --- .../state/chain_time_helpers_test.go | 30 +++++++++++++++++-- .../txs/executor/state_changes_test.go | 8 ++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/state/chain_time_helpers_test.go b/vms/platformvm/state/chain_time_helpers_test.go index 7e1a1786eb61..d93ab1bfd48c 100644 --- a/vms/platformvm/state/chain_time_helpers_test.go +++ b/vms/platformvm/state/chain_time_helpers_test.go @@ -69,7 +69,11 @@ func TestNextBlockTime(t *testing.T) { s.SetTimestamp(test.chainTime) clk.Set(test.now) - actualTime, actualCapped, err := NextBlockTime(s, &clk) + actualTime, actualCapped, err := NextBlockTime( + genesis.LocalParams.ValidatorFeeConfig, + s, + &clk, + ) require.NoError(err) require.Equal(test.expectedTime.Local(), actualTime.Local()) require.Equal(test.expectedCapped, actualCapped) @@ -81,6 +85,7 @@ func TestGetNextStakerChangeTime(t *testing.T) { tests := []struct { name string pending []*Staker + sovs []SubnetOnlyValidator maxTime time.Time expected time.Time }{ @@ -107,6 +112,20 @@ func TestGetNextStakerChangeTime(t *testing.T) { maxTime: mockable.MaxTime, expected: genesistest.DefaultValidatorStartTime.Add(time.Second), }, + { + name: "current and subnet only validators", + sovs: []SubnetOnlyValidator{ + { + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + Weight: 1, + EndAccumulatedFee: 1, // This validator should be evicted in 1 second. + }, + }, + maxTime: mockable.MaxTime, + expected: genesistest.DefaultValidatorStartTime.Add(time.Second), + }, { name: "restricted timestamp", maxTime: genesistest.DefaultValidatorStartTime, @@ -122,8 +141,15 @@ func TestGetNextStakerChangeTime(t *testing.T) { for _, staker := range test.pending { require.NoError(s.PutPendingValidator(staker)) } + for _, sov := range test.sovs { + require.NoError(s.PutSubnetOnlyValidator(sov)) + } - actual, err := GetNextStakerChangeTime(s, test.maxTime) + actual, err := GetNextStakerChangeTime( + genesis.LocalParams.ValidatorFeeConfig, + s, + test.maxTime, + ) require.NoError(err) require.Equal(test.expected.Local(), actual.Local()) }) diff --git a/vms/platformvm/txs/executor/state_changes_test.go b/vms/platformvm/txs/executor/state_changes_test.go index 990d84b94ae4..6ae427129201 100644 --- a/vms/platformvm/txs/executor/state_changes_test.go +++ b/vms/platformvm/txs/executor/state_changes_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/upgrade/upgradetest" "github.com/ava-labs/avalanchego/utils/iterator" @@ -80,6 +81,7 @@ func TestAdvanceTimeTo_UpdatesFeeState(t *testing.T) { // Ensure the invariant that [nextTime <= nextStakerChangeTime] on // AdvanceTimeTo is maintained. nextStakerChangeTime, err := state.GetNextStakerChangeTime( + genesis.LocalParams.ValidatorFeeConfig, s, mockable.MaxTime, ) @@ -188,7 +190,11 @@ func TestAdvanceTimeTo_RemovesStaleExpiries(t *testing.T) { // Ensure the invariant that [newTime <= nextStakerChangeTime] on // AdvanceTimeTo is maintained. - nextStakerChangeTime, err := state.GetNextStakerChangeTime(s, mockable.MaxTime) + nextStakerChangeTime, err := state.GetNextStakerChangeTime( + genesis.LocalParams.ValidatorFeeConfig, + s, + mockable.MaxTime, + ) require.NoError(err) require.False(newTime.After(nextStakerChangeTime)) From 39fff73fb14c86f21938109d298e544d2af1597f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 3 Oct 2024 16:52:16 -0400 Subject: [PATCH 130/199] Fix unit tests --- vms/platformvm/block/executor/proposal_block_test.go | 1 + vms/platformvm/block/executor/standard_block_test.go | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/block/executor/proposal_block_test.go b/vms/platformvm/block/executor/proposal_block_test.go index e55506a381c2..74dbce3c37e9 100644 --- a/vms/platformvm/block/executor/proposal_block_test.go +++ b/vms/platformvm/block/executor/proposal_block_test.go @@ -219,6 +219,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { ), nil }).AnyTimes() onParentAccept.EXPECT().GetPendingStakerIterator().Return(iterator.Empty[*state.Staker]{}, nil).AnyTimes() + onParentAccept.EXPECT().GetActiveSubnetOnlyValidatorsIterator().Return(iterator.Empty[state.SubnetOnlyValidator]{}, nil).AnyTimes() onParentAccept.EXPECT().GetExpiryIterator().Return(iterator.Empty[state.ExpiryEntry]{}, nil).AnyTimes() onParentAccept.EXPECT().GetDelegateeReward(constants.PrimaryNetworkID, unsignedNextStakerTx.NodeID()).Return(uint64(0), nil).AnyTimes() diff --git a/vms/platformvm/block/executor/standard_block_test.go b/vms/platformvm/block/executor/standard_block_test.go index 4f2bc44023ae..15f1e8db4fd2 100644 --- a/vms/platformvm/block/executor/standard_block_test.go +++ b/vms/platformvm/block/executor/standard_block_test.go @@ -129,9 +129,8 @@ func TestBanffStandardBlockTimeVerification(t *testing.T) { ), nil }).AnyTimes() - // no pending stakers onParentAccept.EXPECT().GetPendingStakerIterator().Return(iterator.Empty[*state.Staker]{}, nil).AnyTimes() - // no expiries + onParentAccept.EXPECT().GetActiveSubnetOnlyValidatorsIterator().Return(iterator.Empty[state.SubnetOnlyValidator]{}, nil).AnyTimes() onParentAccept.EXPECT().GetExpiryIterator().Return(iterator.Empty[state.ExpiryEntry]{}, nil).AnyTimes() onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() From 232b06567fd1793f8377a8d08c5f10d1a7f2f31d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 3 Oct 2024 17:11:03 -0400 Subject: [PATCH 131/199] Fix executor unit tests --- .../txs/executor/standard_tx_executor.go | 2 +- .../txs/executor/standard_tx_executor_test.go | 32 +++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 7e74d2459e08..098051c0dab6 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -612,7 +612,7 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { return err } - var txID = e.Tx.ID() + txID := e.Tx.ID() // Consume the UTXOS avax.Consume(e.State, tx.Ins) diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index c561877c421d..afdd3ec14940 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -2554,6 +2554,10 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { require.NoError(err) // Create the ConvertSubnetTx + const ( + weight = 1 + balance = 1 + ) var ( wallet = txstest.NewWallet( t, @@ -2566,17 +2570,18 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { ) chainID = ids.GenerateTestID() address = utils.RandomBytes(32) + pop = signer.NewProofOfPossession(sk) ) convertSubnetTx, err := wallet.IssueConvertSubnetTx( subnetID, chainID, address, - []txs.ConvertSubnetValidator{ + []*txs.ConvertSubnetValidator{ { - NodeID: nodeID, - Weight: 1, - Balance: 1, - Signer: signer.NewProofOfPossession(sk), + NodeID: nodeID.Bytes(), + Weight: weight, + Balance: balance, + Signer: *pop, RemainingBalanceOwner: message.PChainOwner{}, DeactivationOwner: message.PChainOwner{}, }, @@ -2622,10 +2627,23 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { require.Equal(expectedUTXO, utxo) } - // TODO: Populate the conversionID + expectedConversionID, err := message.SubnetConversionID(message.SubnetConversionData{ + SubnetID: subnetID, + ManagerChainID: chainID, + ManagerAddress: address, + Validators: []message.SubnetConversionValidatorData{ + { + NodeID: nodeID.Bytes(), + BLSPublicKey: pop.PublicKey, + Weight: weight, + }, + }, + }) + require.NoError(err) + stateConversionID, stateChainID, stateAddress, err := diff.GetSubnetConversion(subnetID) require.NoError(err) - require.Zero(stateConversionID) + require.Equal(expectedConversionID, stateConversionID) require.Equal(chainID, stateChainID) require.Equal(address, stateAddress) }) From 51c7c7a4b8876be80d4f6740484b002665a88b86 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 3 Oct 2024 17:22:37 -0400 Subject: [PATCH 132/199] Fix fee unit tests --- vms/platformvm/txs/fee/complexity_test.go | 37 +++++++---------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/vms/platformvm/txs/fee/complexity_test.go b/vms/platformvm/txs/fee/complexity_test.go index f7305dfb8d51..d0767317df3b 100644 --- a/vms/platformvm/txs/fee/complexity_test.go +++ b/vms/platformvm/txs/fee/complexity_test.go @@ -370,15 +370,15 @@ func TestInputComplexity(t *testing.T) { func TestConvertSubnetValidatorComplexity(t *testing.T) { tests := []struct { - name string - vdr txs.ConvertSubnetValidator - expected gas.Dimensions - expectedErr error + name string + vdr txs.ConvertSubnetValidator + expected gas.Dimensions }{ { name: "any can spend", vdr: txs.ConvertSubnetValidator{ - Signer: &signer.ProofOfPossession{}, + NodeID: make([]byte, ids.NodeIDLen), + Signer: signer.ProofOfPossession{}, RemainingBalanceOwner: message.PChainOwner{}, DeactivationOwner: message.PChainOwner{}, }, @@ -388,12 +388,12 @@ func TestConvertSubnetValidatorComplexity(t *testing.T) { gas.DBWrite: 4, gas.Compute: 0, // TODO: implement }, - expectedErr: nil, }, { name: "single remaining balance owner", vdr: txs.ConvertSubnetValidator{ - Signer: &signer.ProofOfPossession{}, + NodeID: make([]byte, ids.NodeIDLen), + Signer: signer.ProofOfPossession{}, RemainingBalanceOwner: message.PChainOwner{ Threshold: 1, Addresses: []ids.ShortID{ @@ -408,12 +408,12 @@ func TestConvertSubnetValidatorComplexity(t *testing.T) { gas.DBWrite: 4, gas.Compute: 0, // TODO: implement }, - expectedErr: nil, }, { name: "single deactivation owner", vdr: txs.ConvertSubnetValidator{ - Signer: &signer.ProofOfPossession{}, + NodeID: make([]byte, ids.NodeIDLen), + Signer: signer.ProofOfPossession{}, RemainingBalanceOwner: message.PChainOwner{}, DeactivationOwner: message.PChainOwner{ Threshold: 1, @@ -428,31 +428,16 @@ func TestConvertSubnetValidatorComplexity(t *testing.T) { gas.DBWrite: 4, gas.Compute: 0, // TODO: implement }, - expectedErr: nil, - }, - { - name: "invalid signer", - vdr: txs.ConvertSubnetValidator{ - Signer: nil, - RemainingBalanceOwner: message.PChainOwner{}, - DeactivationOwner: message.PChainOwner{}, - }, - expected: gas.Dimensions{}, - expectedErr: errUnsupportedSigner, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { require := require.New(t) - actual, err := ConvertSubnetValidatorComplexity(test.vdr) - require.ErrorIs(err, test.expectedErr) + actual, err := ConvertSubnetValidatorComplexity(&test.vdr) + require.NoError(err) require.Equal(test.expected, actual) - if err != nil { - return - } - vdrBytes, err := txs.Codec.Marshal(txs.CodecVersion, test.vdr) require.NoError(err) From 3ebeaba6fd6c0a951b6ae74af9d5bcc7600154c8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 3 Oct 2024 18:06:51 -0400 Subject: [PATCH 133/199] Fix more unit tests --- vms/platformvm/txs/convert_subnet_tx_test.go | 38 ++++++++++--------- .../txs/convert_subnet_tx_test_complex.json | 13 +++++-- wallet/chain/p/builder_test.go | 10 ++--- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/vms/platformvm/txs/convert_subnet_tx_test.go b/vms/platformvm/txs/convert_subnet_tx_test.go index e3b5752142f2..c627c9711632 100644 --- a/vms/platformvm/txs/convert_subnet_tx_test.go +++ b/vms/platformvm/txs/convert_subnet_tx_test.go @@ -125,7 +125,7 @@ func TestConvertSubnetTxSerialization(t *testing.T) { Subnet: subnetID, ChainID: managerChainID, Address: managerAddress, - Validators: []ConvertSubnetValidator{}, + Validators: []*ConvertSubnetValidator{}, SubnetAuth: &secp256k1fx.Input{ SigIndices: []uint32{3}, }, @@ -295,7 +295,7 @@ func TestConvertSubnetTxSerialization(t *testing.T) { Subnet: subnetID, ChainID: managerChainID, Address: managerAddress, - Validators: []ConvertSubnetValidator{ + Validators: []*ConvertSubnetValidator{ { NodeID: nodeID[:], Weight: 0x0102030405060708, @@ -471,6 +471,8 @@ func TestConvertSubnetTxSerialization(t *testing.T) { // number of validators 0x00, 0x00, 0x00, 0x01, // Validators[0] + // node ID length + 0x00, 0x00, 0x00, 0x14, // node ID 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, @@ -479,8 +481,6 @@ func TestConvertSubnetTxSerialization(t *testing.T) { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // balance 0x00, 0x00, 0x00, 0x00, 0x3b, 0x9a, 0xca, 0x00, - // signer type ID - 0x00, 0x00, 0x00, 0x1c, // BLS compressed public key 0xaf, 0xf4, 0xac, 0xb4, 0xc5, 0x43, 0x9b, 0x5d, 0x42, 0x6c, 0xad, 0xf9, 0xe9, 0x46, 0xd3, 0xa4, @@ -501,15 +501,19 @@ func TestConvertSubnetTxSerialization(t *testing.T) { 0xd5, 0x55, 0x5d, 0xa5, 0xc4, 0x89, 0x87, 0x2e, 0x02, 0xb7, 0xe5, 0x22, 0x7b, 0x77, 0x55, 0x0a, 0xf1, 0x33, 0x0e, 0x5a, 0x71, 0xf8, 0xc3, 0x68, - // RemainingBalanceOwner type ID - 0x00, 0x00, 0x00, 0x0b, - // locktime - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // threshold + // RemainingBalanceOwner threshold 0x00, 0x00, 0x00, 0x01, - // number of addresses + // RemainingBalanceOwner number of addresses + 0x00, 0x00, 0x00, 0x01, + // RemainingBalanceOwner Addrs[0] + 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0x44, 0x55, 0x66, 0x77, + // DeactivationOwner threshold + 0x00, 0x00, 0x00, 0x01, + // DeactivationOwner number of addresses 0x00, 0x00, 0x00, 0x01, - // Addrs[0] + // DeactivationOwner Addrs[0] 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x44, 0x55, 0x66, 0x77, @@ -558,7 +562,7 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { } validSubnetID = ids.GenerateTestID() invalidAddress = make(types.JSONByteSlice, MaxSubnetAddressLength+1) - validValidators = []ConvertSubnetValidator{ + validValidators = []*ConvertSubnetValidator{ { NodeID: utils.RandomBytes(ids.NodeIDLen), Weight: 1, @@ -635,7 +639,7 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { tx: &ConvertSubnetTx{ BaseTx: validBaseTx, Subnet: validSubnetID, - Validators: []ConvertSubnetValidator{ + Validators: []*ConvertSubnetValidator{ { NodeID: []byte{ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -660,7 +664,7 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { tx: &ConvertSubnetTx{ BaseTx: validBaseTx, Subnet: validSubnetID, - Validators: []ConvertSubnetValidator{ + Validators: []*ConvertSubnetValidator{ { NodeID: utils.RandomBytes(ids.NodeIDLen), Weight: 0, @@ -678,7 +682,7 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { tx: &ConvertSubnetTx{ BaseTx: validBaseTx, Subnet: validSubnetID, - Validators: []ConvertSubnetValidator{ + Validators: []*ConvertSubnetValidator{ { NodeID: utils.RandomBytes(ids.NodeIDLen), Weight: 1, @@ -696,7 +700,7 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { tx: &ConvertSubnetTx{ BaseTx: validBaseTx, Subnet: validSubnetID, - Validators: []ConvertSubnetValidator{ + Validators: []*ConvertSubnetValidator{ { NodeID: utils.RandomBytes(ids.NodeIDLen), Weight: 1, @@ -716,7 +720,7 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { tx: &ConvertSubnetTx{ BaseTx: validBaseTx, Subnet: validSubnetID, - Validators: []ConvertSubnetValidator{ + Validators: []*ConvertSubnetValidator{ { NodeID: utils.RandomBytes(ids.NodeIDLen), Weight: 1, diff --git a/vms/platformvm/txs/convert_subnet_tx_test_complex.json b/vms/platformvm/txs/convert_subnet_tx_test_complex.json index a0de0c718e9a..926c475fec31 100644 --- a/vms/platformvm/txs/convert_subnet_tx_test_complex.json +++ b/vms/platformvm/txs/convert_subnet_tx_test_complex.json @@ -77,7 +77,7 @@ "address": "0x000000000000000000000000000000000000dead", "validators": [ { - "nodeID": "NodeID-2ZbTY9GatRTrfinAoYiYLcf6CvrPAUYgo", + "nodeID": "0x1122334455667788112233445566778811223344", "weight": 72623859790382856, "balance": 1000000000, "signer": { @@ -85,11 +85,16 @@ "proofOfPossession": "0x8cfd7909d153b9604b62b143ba36207bb7e64867424480202a67dc68768346d95c90983c2d279c64c43c51136b2a05e01602d52aa6376fda17fa6e2a18a083e49d9c450eab7b89b1d5555da5c489872e02b7e5227b77550af1330e5a71f8c368" }, "remainingBalanceOwner": { + "threshold": 1, "addresses": [ "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" - ], - "locktime": 0, - "threshold": 1 + ] + }, + "deactivationOwner": { + "threshold": 1, + "addresses": [ + "7EKFm18KvWqcxMCNgpBSN51pJnEr1cVUb" + ] } } ], diff --git a/wallet/chain/p/builder_test.go b/wallet/chain/p/builder_test.go index adbf03705235..ed71768b0969 100644 --- a/wallet/chain/p/builder_test.go +++ b/wallet/chain/p/builder_test.go @@ -678,12 +678,12 @@ func TestConvertSubnetTx(t *testing.T) { var ( chainID = ids.GenerateTestID() address = utils.RandomBytes(32) - validators = []txs.ConvertSubnetValidator{ + validators = []*txs.ConvertSubnetValidator{ { - NodeID: ids.GenerateTestNodeID(), + NodeID: utils.RandomBytes(ids.NodeIDLen), Weight: rand.Uint64(), //#nosec G404 Balance: units.Avax, - Signer: signer.NewProofOfPossession(sk0), + Signer: *signer.NewProofOfPossession(sk0), RemainingBalanceOwner: message.PChainOwner{ Threshold: 1, Addresses: []ids.ShortID{ @@ -698,10 +698,10 @@ func TestConvertSubnetTx(t *testing.T) { }, }, { - NodeID: ids.GenerateTestNodeID(), + NodeID: utils.RandomBytes(ids.NodeIDLen), Weight: rand.Uint64(), //#nosec G404 Balance: 2 * units.Avax, - Signer: signer.NewProofOfPossession(sk1), + Signer: *signer.NewProofOfPossession(sk1), RemainingBalanceOwner: message.PChainOwner{}, DeactivationOwner: message.PChainOwner{}, }, From 363e2be823d2aef1abee8b9602d4c0988e91b306 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 3 Oct 2024 18:31:59 -0400 Subject: [PATCH 134/199] Fix e2e test --- tests/e2e/p/permissionless_layer_one.go | 72 +++++++++++++++++++------ vms/platformvm/client.go | 2 + 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 844b3a257341..e3eab63ebffc 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -13,7 +13,11 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/tests/fixture/e2e" "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/vms/platformvm" + "github.com/ava-labs/avalanchego/vms/platformvm/signer" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) @@ -63,38 +67,74 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { res, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) require.NoError(err) - require.Equal(platformvm.GetSubnetClientResponse{ - IsPermissioned: true, - ControlKeys: []ids.ShortID{ - keychain.Keys[0].Address(), + require.Equal( + platformvm.GetSubnetClientResponse{ + IsPermissioned: true, + ControlKeys: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + Threshold: 1, }, - Threshold: 1, - }, res) + res, + ) + + const weight = 100 + var ( + chainID = ids.GenerateTestID() + address = []byte{'a', 'd', 'd', 'r', 'e', 's', 's'} + nodeID = ids.GenerateTestNodeID() + ) - chainID := ids.GenerateTestID() - address := []byte{'a', 'd', 'd', 'r', 'e', 's', 's'} + sk, err := bls.NewSecretKey() + require.NoError(err) + pop := signer.NewProofOfPossession(sk) tc.By("issuing a ConvertSubnetTx") _, err = pWallet.IssueConvertSubnetTx( subnetID, chainID, address, + []*txs.ConvertSubnetValidator{ + { + NodeID: nodeID.Bytes(), + Weight: weight, + Signer: *pop, + }, + }, tc.WithDefaultContext(), ) require.NoError(err) + expectedConversionID, err := message.SubnetConversionID(message.SubnetConversionData{ + SubnetID: subnetID, + ManagerChainID: chainID, + ManagerAddress: address, + Validators: []message.SubnetConversionValidatorData{ + { + NodeID: nodeID.Bytes(), + BLSPublicKey: pop.PublicKey, + Weight: weight, + }, + }, + }) + require.NoError(err) + tc.By("verifying the Permissioned Subnet was converted to a Permissionless L1") res, err = pClient.GetSubnet(tc.DefaultContext(), subnetID) require.NoError(err) - require.Equal(platformvm.GetSubnetClientResponse{ - IsPermissioned: false, - ControlKeys: []ids.ShortID{ - keychain.Keys[0].Address(), + require.Equal( + platformvm.GetSubnetClientResponse{ + IsPermissioned: false, + ControlKeys: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + Threshold: 1, + ConversionID: expectedConversionID, + ManagerChainID: chainID, + ManagerAddress: address, }, - Threshold: 1, - ManagerChainID: chainID, - ManagerAddress: address, - }, res) + res, + ) }) }) diff --git a/vms/platformvm/client.go b/vms/platformvm/client.go index 54554f37d92b..9b03a3448732 100644 --- a/vms/platformvm/client.go +++ b/vms/platformvm/client.go @@ -236,6 +236,7 @@ type GetSubnetClientResponse struct { // subnet transformation tx ID for a permissionless subnet SubnetTransformationTxID ids.ID // subnet manager information for a permissionless L1 + ConversionID ids.ID ManagerChainID ids.ID ManagerAddress []byte } @@ -259,6 +260,7 @@ func (c *client) GetSubnet(ctx context.Context, subnetID ids.ID, options ...rpc. Threshold: uint32(res.Threshold), Locktime: uint64(res.Locktime), SubnetTransformationTxID: res.SubnetTransformationTxID, + ConversionID: res.ConversionID, ManagerChainID: res.ManagerChainID, ManagerAddress: res.ManagerAddress, }, nil From 73fb07caa690c3b2cac2615be982d3257c0a1c11 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 3 Oct 2024 18:47:00 -0400 Subject: [PATCH 135/199] wip fix ci --- vms/platformvm/block/builder/helpers_test.go | 5 ++++- vms/platformvm/network/network_test.go | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/vms/platformvm/block/builder/helpers_test.go b/vms/platformvm/block/builder/helpers_test.go index faba1b1ed695..fa3fde1141a1 100644 --- a/vms/platformvm/block/builder/helpers_test.go +++ b/vms/platformvm/block/builder/helpers_test.go @@ -166,8 +166,11 @@ func newEnvironment(t *testing.T, f upgradetest.Fork) *environment { //nolint:un res.mempool, res.backend.Config.PartialSyncPrimaryNetwork, res.sender, + &res.ctx.Lock, + res.state, + res.ctx.WarpSigner, registerer, - network.DefaultConfig, + config.DefaultNetworkConfig, ) require.NoError(err) diff --git a/vms/platformvm/network/network_test.go b/vms/platformvm/network/network_test.go index 6f990e2196ab..d47ef1fcf614 100644 --- a/vms/platformvm/network/network_test.go +++ b/vms/platformvm/network/network_test.go @@ -176,6 +176,9 @@ func TestNetworkIssueTxFromRPC(t *testing.T) { tt.mempoolFunc(ctrl), tt.partialSyncPrimaryNetwork, tt.appSenderFunc(ctrl), + nil, // TODO: Populate and test + nil, + nil, prometheus.NewRegistry(), testConfig, ) From 80a7cbeb407fadc85b52b7f5dd8d72cf85c358c7 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 14:43:39 -0400 Subject: [PATCH 136/199] fix lint --- .../register-subnet-validator/main.go | 52 +++++++++++++++---- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/wallet/subnet/primary/examples/register-subnet-validator/main.go b/wallet/subnet/primary/examples/register-subnet-validator/main.go index 2eb92b164ff0..05d51aa1719b 100644 --- a/wallet/subnet/primary/examples/register-subnet-validator/main.go +++ b/wallet/subnet/primary/examples/register-subnet-validator/main.go @@ -6,13 +6,16 @@ package main import ( "context" "encoding/hex" + "encoding/json" "log" + "os" "time" "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" @@ -26,9 +29,9 @@ func main() { uri := "http://localhost:9710" kc := secp256k1fx.NewKeychain(key) subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") - chainID := ids.FromStringOrPanic("2f23iBApzAwJy1LRrgGZ3pGGK4S6UrakHMejZsQiwKy4rKfnzx") + chainID := ids.FromStringOrPanic("2BMFrJ9xeh5JdwZEx6uuFcjfZC2SV2hdbMT8ee5HrvjtfJb5br") addressHex := "" - weight := units.Schmeckle + weight := uint64(1) address, err := hex.DecodeString(addressHex) if err != nil { @@ -38,6 +41,16 @@ func main() { ctx := context.Background() infoClient := info.NewClient(uri) + skBytes, err := os.ReadFile("/Users/stephen/.avalanchego/staking/signer.key") + if err != nil { + log.Fatalf("failed to read signer key: %s\n", err) + } + + sk, err := bls.SecretKeyFromBytes(skBytes) + if err != nil { + log.Fatalf("failed to parse secret key: %s\n", err) + } + nodeInfoStartTime := time.Now() nodeID, nodePoP, err := infoClient.GetNodeID(ctx) if err != nil { @@ -65,13 +78,20 @@ func main() { addressedCallPayload, err := message.NewRegisterSubnetValidator( subnetID, nodeID, - weight, nodePoP.PublicKey, uint64(time.Now().Add(5*time.Minute).Unix()), + message.PChainOwner{}, + message.PChainOwner{}, + weight, ) if err != nil { log.Fatalf("failed to create RegisterSubnetValidator message: %s\n", err) } + addressedCallPayloadJSON, err := json.MarshalIndent(addressedCallPayload, "", "\t") + if err != nil { + log.Fatalf("failed to marshal RegisterSubnetValidator message: %s\n", err) + } + log.Println(string(addressedCallPayloadJSON)) addressedCall, err := payload.NewAddressedCall( address, @@ -90,25 +110,35 @@ func main() { log.Fatalf("failed to create unsigned Warp message: %s\n", err) } + signers := set.NewBits() + signers.Add(0) // [signers] has weight from [vdr[0]] + + unsignedBytes := unsignedWarp.Bytes() + sig := bls.Sign(sk, unsignedBytes) + sigBytes := [bls.SignatureLen]byte{} + copy(sigBytes[:], bls.SignatureToBytes(sig)) + warp, err := warp.NewMessage( unsignedWarp, - &warp.BitSetSignature{}, + &warp.BitSetSignature{ + Signers: signers.Bytes(), + Signature: sigBytes, + }, ) if err != nil { log.Fatalf("failed to create Warp message: %s\n", err) } - convertSubnetStartTime := time.Now() - addValidatorTx, err := pWallet.IssueRegisterSubnetValidatorTx( + registerSubnetValidatorStartTime := time.Now() + registerSubnetValidatorTx, err := pWallet.IssueRegisterSubnetValidatorTx( units.Avax, nodePoP.ProofOfPossession, - &secp256k1fx.OutputOwners{}, warp.Bytes(), ) if err != nil { - log.Fatalf("failed to issue add subnet validator transaction: %s\n", err) + log.Fatalf("failed to issue register subnet validator transaction: %s\n", err) } - validationID := hashing.ComputeHash256Array(addressedCallPayload.Bytes()) - log.Printf("added new subnet validator %s to %s with %s as %s in %s\n", nodeID, subnetID, addValidatorTx.ID(), validationID, time.Since(convertSubnetStartTime)) + validationID := addressedCallPayload.ValidationID() + log.Printf("registered new subnet validator %s to subnet %s with txID %s as validationID %s in %s\n", nodeID, subnetID, registerSubnetValidatorTx.ID(), validationID, time.Since(registerSubnetValidatorStartTime)) } From 0ceeb16762305f26a0f1ba05410a7749dc178ec9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 14:46:03 -0400 Subject: [PATCH 137/199] Fix lint --- .../set-subnet-validator-weight/main.go | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go b/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go index 082268505a65..35e7ce264104 100644 --- a/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go +++ b/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go @@ -6,11 +6,15 @@ package main import ( "context" "encoding/hex" + "encoding/json" "log" + "os" "time" "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -22,17 +26,27 @@ func main() { key := genesis.EWOQKey uri := primary.LocalAPIURI kc := secp256k1fx.NewKeychain(key) - chainID := ids.FromStringOrPanic("2ko3NCPzHeneKWcYfy55pgAgU1LV9Q9XNrNv2sWG4W2XzE3ViV") + chainID := ids.FromStringOrPanic("2BMFrJ9xeh5JdwZEx6uuFcjfZC2SV2hdbMT8ee5HrvjtfJb5br") addressHex := "" - validationID := ids.FromStringOrPanic("225kHLzuaBd6rhxZ8aq91kmgLJyPTFtTFVAWJDaPyKRdDiTpQo") + validationID := ids.FromStringOrPanic("2Y3ZZZXxpzm46geqVuqFXeSFVbeKihgrfeXRDaiF4ds6R2N8M5") nonce := uint64(1) - weight := uint64(0) + weight := uint64(2) address, err := hex.DecodeString(addressHex) if err != nil { log.Fatalf("failed to decode address %q: %s\n", addressHex, err) } + skBytes, err := os.ReadFile("/Users/stephen/.avalanchego/staking/signer.key") + if err != nil { + log.Fatalf("failed to read signer key: %s\n", err) + } + + sk, err := bls.SecretKeyFromBytes(skBytes) + if err != nil { + log.Fatalf("failed to parse secret key: %s\n", err) + } + // MakeWallet fetches the available UTXOs owned by [kc] on the network that // [uri] is hosting and registers [subnetID]. walletSyncStartTime := time.Now() @@ -51,14 +65,19 @@ func main() { pWallet := wallet.P() context := pWallet.Builder().Context() - addressedCallPayload, err := message.NewSetSubnetValidatorWeight( + addressedCallPayload, err := message.NewSubnetValidatorWeight( validationID, nonce, weight, ) if err != nil { - log.Fatalf("failed to create SetSubnetValidatorWeight message: %s\n", err) + log.Fatalf("failed to create SubnetValidatorWeight message: %s\n", err) + } + addressedCallPayloadJSON, err := json.MarshalIndent(addressedCallPayload, "", "\t") + if err != nil { + log.Fatalf("failed to marshal SubnetValidatorWeight message: %s\n", err) } + log.Println(string(addressedCallPayloadJSON)) addressedCall, err := payload.NewAddressedCall( address, @@ -77,9 +96,20 @@ func main() { log.Fatalf("failed to create unsigned Warp message: %s\n", err) } + signers := set.NewBits() + signers.Add(0) // [signers] has weight from [vdr[0]] + + unsignedBytes := unsignedWarp.Bytes() + sig := bls.Sign(sk, unsignedBytes) + sigBytes := [bls.SignatureLen]byte{} + copy(sigBytes[:], bls.SignatureToBytes(sig)) + warp, err := warp.NewMessage( unsignedWarp, - &warp.BitSetSignature{}, + &warp.BitSetSignature{ + Signers: signers.Bytes(), + Signature: sigBytes, + }, ) if err != nil { log.Fatalf("failed to create Warp message: %s\n", err) From 63155aaf3de74efa83a31feb93b1ae59797de4f9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 14:48:31 -0400 Subject: [PATCH 138/199] merged --- .../primary/examples/register-subnet-validator/main.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/wallet/subnet/primary/examples/register-subnet-validator/main.go b/wallet/subnet/primary/examples/register-subnet-validator/main.go index 99dc1d769e90..fe8d5d9b08ee 100644 --- a/wallet/subnet/primary/examples/register-subnet-validator/main.go +++ b/wallet/subnet/primary/examples/register-subnet-validator/main.go @@ -28,13 +28,8 @@ func main() { key := genesis.EWOQKey uri := "http://localhost:9650" kc := secp256k1fx.NewKeychain(key) -<<<<<<< HEAD - subnetID := ids.FromStringOrPanic("2eZYSgCU738xN7aRw47NsBUPqnKkoqJMYUJexTsX19VdTNSZc9") - chainID := ids.FromStringOrPanic("JCuZD3rg8dEEeY5cJwQxwSj6Shqo8DtRynBCt1nXkBXCouZVw") -======= subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") chainID := ids.FromStringOrPanic("2BMFrJ9xeh5JdwZEx6uuFcjfZC2SV2hdbMT8ee5HrvjtfJb5br") ->>>>>>> implement-acp-77--set-subnet-validator-weight-tx addressHex := "" weight := uint64(1) From 86f51ec8f354e3dad9acbede2536d6749cf59870 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 14:51:09 -0400 Subject: [PATCH 139/199] update --- vms/platformvm/warp/message/payload.go | 2 +- .../warp/message/register_subnet_validator.go | 2 +- .../warp/message/subnet_conversion.go | 2 +- .../message/subnet_validator_registration.go | 2 +- .../warp/message/subnet_validator_weight.go | 2 +- .../examples/sign-subnet-conversion/main.go | 119 +++++++++++ .../main.go | 106 +++++----- .../sign-subnet-validator-removal/main.go | 196 ++++++++++++++++++ .../sign-subnet-validator-weight/main.go | 127 ++++++++++++ 9 files changed, 503 insertions(+), 55 deletions(-) create mode 100644 wallet/subnet/primary/examples/sign-subnet-conversion/main.go create mode 100644 wallet/subnet/primary/examples/sign-subnet-validator-removal/main.go create mode 100644 wallet/subnet/primary/examples/sign-subnet-validator-weight/main.go diff --git a/vms/platformvm/warp/message/payload.go b/vms/platformvm/warp/message/payload.go index 6903cc22001d..1da0f71aa9b5 100644 --- a/vms/platformvm/warp/message/payload.go +++ b/vms/platformvm/warp/message/payload.go @@ -43,7 +43,7 @@ func Parse(bytes []byte) (Payload, error) { return p, nil } -func initialize(p Payload) error { +func Initialize(p Payload) error { bytes, err := Codec.Marshal(CodecVersion, &p) if err != nil { return fmt.Errorf("couldn't marshal %T payload: %w", p, err) diff --git a/vms/platformvm/warp/message/register_subnet_validator.go b/vms/platformvm/warp/message/register_subnet_validator.go index cf0b1cbcd569..ba4eb3aab168 100644 --- a/vms/platformvm/warp/message/register_subnet_validator.go +++ b/vms/platformvm/warp/message/register_subnet_validator.go @@ -99,7 +99,7 @@ func NewRegisterSubnetValidator( DisableOwner: disableOwner, Weight: weight, } - return msg, initialize(msg) + return msg, Initialize(msg) } // ParseRegisterSubnetValidator parses bytes into an initialized diff --git a/vms/platformvm/warp/message/subnet_conversion.go b/vms/platformvm/warp/message/subnet_conversion.go index a93354fbe446..f1af8e78e5ab 100644 --- a/vms/platformvm/warp/message/subnet_conversion.go +++ b/vms/platformvm/warp/message/subnet_conversion.go @@ -50,7 +50,7 @@ func NewSubnetConversion(id ids.ID) (*SubnetConversion, error) { msg := &SubnetConversion{ ID: id, } - return msg, initialize(msg) + return msg, Initialize(msg) } // ParseSubnetConversion parses bytes into an initialized SubnetConversion. diff --git a/vms/platformvm/warp/message/subnet_validator_registration.go b/vms/platformvm/warp/message/subnet_validator_registration.go index 2ab46c88dc3b..f0e1919b1e24 100644 --- a/vms/platformvm/warp/message/subnet_validator_registration.go +++ b/vms/platformvm/warp/message/subnet_validator_registration.go @@ -34,7 +34,7 @@ func NewSubnetValidatorRegistration( ValidationID: validationID, Registered: registered, } - return msg, initialize(msg) + return msg, Initialize(msg) } // ParseSubnetValidatorRegistration parses bytes into an initialized diff --git a/vms/platformvm/warp/message/subnet_validator_weight.go b/vms/platformvm/warp/message/subnet_validator_weight.go index dcfa6c5a16c0..5b94889a8f03 100644 --- a/vms/platformvm/warp/message/subnet_validator_weight.go +++ b/vms/platformvm/warp/message/subnet_validator_weight.go @@ -46,7 +46,7 @@ func NewSubnetValidatorWeight( Nonce: nonce, Weight: weight, } - return msg, initialize(msg) + return msg, Initialize(msg) } // ParseSubnetValidatorWeight parses bytes into an initialized diff --git a/wallet/subnet/primary/examples/sign-subnet-conversion/main.go b/wallet/subnet/primary/examples/sign-subnet-conversion/main.go new file mode 100644 index 000000000000..87f159855853 --- /dev/null +++ b/wallet/subnet/primary/examples/sign-subnet-conversion/main.go @@ -0,0 +1,119 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "log" + "net/netip" + "time" + + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/protobuf/proto" + + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/peer" + "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/snow/networking/router" + "github.com/ava-labs/avalanchego/utils/compression" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + + p2pmessage "github.com/ava-labs/avalanchego/message" + warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" +) + +func main() { + uri := primary.LocalAPIURI + subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") + conversionID := ids.FromStringOrPanic("d84X4xQeXkAhnLQi2BqDyG6AEGbdJVGJCwx6a4c3UnBhD9vpZ") + infoClient := info.NewClient(uri) + networkID, err := infoClient.GetNetworkID(context.Background()) + if err != nil { + log.Fatalf("failed to fetch network ID: %s\n", err) + } + + subnetConversion, err := warpmessage.NewSubnetConversion(conversionID) + if err != nil { + log.Fatalf("failed to create SubnetConversion message: %s\n", err) + } + + addressedCall, err := payload.NewAddressedCall( + nil, + subnetConversion.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create AddressedCall message: %s\n", err) + } + + unsignedWarp, err := warp.NewUnsignedMessage( + networkID, + constants.PlatformChainID, + addressedCall.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create unsigned Warp message: %s\n", err) + } + + p, err := peer.StartTestPeer( + context.Background(), + netip.AddrPortFrom( + netip.AddrFrom4([4]byte{127, 0, 0, 1}), + 9651, + ), + networkID, + router.InboundHandlerFunc(func(_ context.Context, msg p2pmessage.InboundMessage) { + log.Printf("received %s: %s", msg.Op(), msg.Message()) + }), + ) + if err != nil { + log.Fatalf("failed to start peer: %s\n", err) + } + + mesageBuilder, err := p2pmessage.NewCreator( + logging.NoLog{}, + prometheus.NewRegistry(), + compression.TypeZstd, + time.Hour, + ) + if err != nil { + log.Fatalf("failed to create message builder: %s\n", err) + } + + appRequestPayload, err := proto.Marshal(&sdk.SignatureRequest{ + Message: unsignedWarp.Bytes(), + Justification: subnetID[:], + }) + if err != nil { + log.Fatalf("failed to marshal SignatureRequest: %s\n", err) + } + + appRequest, err := mesageBuilder.AppRequest( + constants.PlatformChainID, + 0, + time.Hour, + p2p.PrefixMessage( + p2p.ProtocolPrefix(p2p.SignatureRequestHandlerID), + appRequestPayload, + ), + ) + if err != nil { + log.Fatalf("failed to create AppRequest: %s\n", err) + } + + p.Send(context.Background(), appRequest) + + time.Sleep(5 * time.Second) + + p.StartClose() + err = p.AwaitClosed(context.Background()) + if err != nil { + log.Fatalf("failed to close peer: %s\n", err) + } +} diff --git a/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go b/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go index 8ba1bc80a098..6506d2477c2c 100644 --- a/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go +++ b/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go @@ -14,14 +14,12 @@ import ( "google.golang.org/protobuf/proto" "github.com/ava-labs/avalanchego/api/info" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/network/peer" "github.com/ava-labs/avalanchego/proto/pb/sdk" "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/utils/compression" "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -33,59 +31,67 @@ import ( var registerSubnetValidatorJSON = []byte(`{ "subnetID": "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof", - "nodeID": "NodeID-9Nm8NNALws11M5J89rXgc46u6L52e7fmz", - "weight": 49463, + "nodeID": "0xb628ee3952a5de80fadd31ab030a67189edb1410", "blsPublicKey": [ + 143, + 167, + 255, + 128, + 221, + 92, + 126, + 190, + 134, + 189, + 157, + 166, + 6, + 55, + 92, + 125, + 223, + 231, + 71, + 85, + 122, + 110, + 110, + 49, + 215, + 14, + 1, + 226, + 146, + 140, + 73, + 75, + 113, + 163, 138, - 25, - 41, - 204, - 242, - 137, - 56, - 208, - 15, - 227, - 216, - 136, + 158, + 34, + 207, + 99, + 36, 137, - 144, - 59, - 120, - 205, - 181, - 230, - 232, - 225, + 55, + 191, + 28, + 186, 24, - 80, - 117, - 225, - 16, - 102, - 93, - 83, - 184, - 245, - 240, - 232, - 97, - 238, 49, - 217, - 4, - 131, - 121, - 19, - 213, - 202, - 233, - 38, - 19, - 70, - 58 + 199 ], - "expiry": 1727548306 + "expiry": 1727975059, + "remainingBalanceOwner": { + "threshold": 0, + "addresses": null + }, + "disableOwner": { + "threshold": 0, + "addresses": null + }, + "weight": 1 }`) func main() { @@ -106,7 +112,7 @@ func main() { log.Fatalf("failed to initialize RegisterSubnetValidator message: %s\n", err) } - var validationID ids.ID = hashing.ComputeHash256Array(registerSubnetValidator.Bytes()) + validationID := registerSubnetValidator.ValidationID() subnetValidatorRegistration, err := warpmessage.NewSubnetValidatorRegistration( validationID, true, diff --git a/wallet/subnet/primary/examples/sign-subnet-validator-removal/main.go b/wallet/subnet/primary/examples/sign-subnet-validator-removal/main.go new file mode 100644 index 000000000000..02ae39abef7d --- /dev/null +++ b/wallet/subnet/primary/examples/sign-subnet-validator-removal/main.go @@ -0,0 +1,196 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "encoding/json" + "log" + "net/netip" + "time" + + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/protobuf/proto" + + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/peer" + "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/snow/networking/router" + "github.com/ava-labs/avalanchego/utils/compression" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + + p2pmessage "github.com/ava-labs/avalanchego/message" + warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" +) + +var registerSubnetValidatorJSON = []byte(`{ + "subnetID": "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof", + "nodeID": "0xb628ee3952a5de80fadd31ab030a67189edb1410", + "blsPublicKey": [ + 143, + 167, + 255, + 128, + 221, + 92, + 126, + 190, + 134, + 189, + 157, + 166, + 6, + 55, + 92, + 125, + 223, + 231, + 71, + 85, + 122, + 110, + 110, + 49, + 215, + 14, + 1, + 226, + 146, + 140, + 73, + 75, + 113, + 163, + 138, + 158, + 34, + 207, + 99, + 36, + 137, + 55, + 191, + 28, + 186, + 24, + 49, + 199 + ], + "expiry": 1727975059, + "remainingBalanceOwner": { + "threshold": 0, + "addresses": null + }, + "disableOwner": { + "threshold": 0, + "addresses": null + }, + "weight": 1 +}`) + +func main() { + uri := primary.LocalAPIURI + infoClient := info.NewClient(uri) + networkID, err := infoClient.GetNetworkID(context.Background()) + if err != nil { + log.Fatalf("failed to fetch network ID: %s\n", err) + } + + var registerSubnetValidator warpmessage.RegisterSubnetValidator + err = json.Unmarshal(registerSubnetValidatorJSON, ®isterSubnetValidator) + if err != nil { + log.Fatalf("failed to unmarshal RegisterSubnetValidator message: %s\n", err) + } + err = warpmessage.Initialize(®isterSubnetValidator) + if err != nil { + log.Fatalf("failed to initialize RegisterSubnetValidator message: %s\n", err) + } + + validationID := registerSubnetValidator.ValidationID() + subnetValidatorRegistration, err := warpmessage.NewSubnetValidatorRegistration( + validationID, + false, + ) + if err != nil { + log.Fatalf("failed to create SubnetValidatorRegistration message: %s\n", err) + } + + addressedCall, err := payload.NewAddressedCall( + nil, + subnetValidatorRegistration.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create AddressedCall message: %s\n", err) + } + + unsignedWarp, err := warp.NewUnsignedMessage( + networkID, + constants.PlatformChainID, + addressedCall.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create unsigned Warp message: %s\n", err) + } + + p, err := peer.StartTestPeer( + context.Background(), + netip.AddrPortFrom( + netip.AddrFrom4([4]byte{127, 0, 0, 1}), + 9651, + ), + networkID, + router.InboundHandlerFunc(func(_ context.Context, msg p2pmessage.InboundMessage) { + log.Printf("received %s: %s", msg.Op(), msg.Message()) + }), + ) + if err != nil { + log.Fatalf("failed to start peer: %s\n", err) + } + + mesageBuilder, err := p2pmessage.NewCreator( + logging.NoLog{}, + prometheus.NewRegistry(), + compression.TypeZstd, + time.Hour, + ) + if err != nil { + log.Fatalf("failed to create message builder: %s\n", err) + } + + appRequestPayload, err := proto.Marshal(&sdk.SignatureRequest{ + Message: unsignedWarp.Bytes(), + Justification: registerSubnetValidator.Bytes(), + }) + if err != nil { + log.Fatalf("failed to marshal SignatureRequest: %s\n", err) + } + + appRequest, err := mesageBuilder.AppRequest( + constants.PlatformChainID, + 0, + time.Hour, + p2p.PrefixMessage( + p2p.ProtocolPrefix(p2p.SignatureRequestHandlerID), + appRequestPayload, + ), + ) + if err != nil { + log.Fatalf("failed to create AppRequest: %s\n", err) + } + + p.Send(context.Background(), appRequest) + + time.Sleep(5 * time.Second) + + p.StartClose() + err = p.AwaitClosed(context.Background()) + if err != nil { + log.Fatalf("failed to close peer: %s\n", err) + } +} diff --git a/wallet/subnet/primary/examples/sign-subnet-validator-weight/main.go b/wallet/subnet/primary/examples/sign-subnet-validator-weight/main.go new file mode 100644 index 000000000000..26a8dd1cfcb3 --- /dev/null +++ b/wallet/subnet/primary/examples/sign-subnet-validator-weight/main.go @@ -0,0 +1,127 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "encoding/json" + "log" + "net/netip" + "time" + + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/protobuf/proto" + + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/peer" + "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/snow/networking/router" + "github.com/ava-labs/avalanchego/utils/compression" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + + p2pmessage "github.com/ava-labs/avalanchego/message" + warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" +) + +var subnetValidatorWeightJSON = []byte(`{ + "validationID": "2Y3ZZZXxpzm46geqVuqFXeSFVbeKihgrfeXRDaiF4ds6R2N8M5", + "nonce": 1, + "weight": 2 +}`) + +func main() { + uri := primary.LocalAPIURI + infoClient := info.NewClient(uri) + networkID, err := infoClient.GetNetworkID(context.Background()) + if err != nil { + log.Fatalf("failed to fetch network ID: %s\n", err) + } + + var subnetValidatorWeight warpmessage.SubnetValidatorWeight + err = json.Unmarshal(subnetValidatorWeightJSON, &subnetValidatorWeight) + if err != nil { + log.Fatalf("failed to unmarshal SubnetValidatorWeight message: %s\n", err) + } + err = warpmessage.Initialize(&subnetValidatorWeight) + if err != nil { + log.Fatalf("failed to initialize SubnetValidatorWeight message: %s\n", err) + } + + addressedCall, err := payload.NewAddressedCall( + nil, + subnetValidatorWeight.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create AddressedCall message: %s\n", err) + } + + unsignedWarp, err := warp.NewUnsignedMessage( + networkID, + constants.PlatformChainID, + addressedCall.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create unsigned Warp message: %s\n", err) + } + + p, err := peer.StartTestPeer( + context.Background(), + netip.AddrPortFrom( + netip.AddrFrom4([4]byte{127, 0, 0, 1}), + 9651, + ), + networkID, + router.InboundHandlerFunc(func(_ context.Context, msg p2pmessage.InboundMessage) { + log.Printf("received %s: %s", msg.Op(), msg.Message()) + }), + ) + if err != nil { + log.Fatalf("failed to start peer: %s\n", err) + } + + mesageBuilder, err := p2pmessage.NewCreator( + logging.NoLog{}, + prometheus.NewRegistry(), + compression.TypeZstd, + time.Hour, + ) + if err != nil { + log.Fatalf("failed to create message builder: %s\n", err) + } + + appRequestPayload, err := proto.Marshal(&sdk.SignatureRequest{ + Message: unsignedWarp.Bytes(), + }) + if err != nil { + log.Fatalf("failed to marshal SignatureRequest: %s\n", err) + } + + appRequest, err := mesageBuilder.AppRequest( + constants.PlatformChainID, + 0, + time.Hour, + p2p.PrefixMessage( + p2p.ProtocolPrefix(p2p.SignatureRequestHandlerID), + appRequestPayload, + ), + ) + if err != nil { + log.Fatalf("failed to create AppRequest: %s\n", err) + } + + p.Send(context.Background(), appRequest) + + time.Sleep(5 * time.Second) + + p.StartClose() + err = p.AwaitClosed(context.Background()) + if err != nil { + log.Fatalf("failed to close peer: %s\n", err) + } +} From 97488449c0d94e89b17e6f090c345bbb5b22c7f0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 14:52:50 -0400 Subject: [PATCH 140/199] Add example --- .../primary/examples/increase-balance/main.go | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 wallet/subnet/primary/examples/increase-balance/main.go diff --git a/wallet/subnet/primary/examples/increase-balance/main.go b/wallet/subnet/primary/examples/increase-balance/main.go new file mode 100644 index 000000000000..e0a1af0c1093 --- /dev/null +++ b/wallet/subnet/primary/examples/increase-balance/main.go @@ -0,0 +1,56 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "log" + "time" + + "github.com/ava-labs/avalanchego/genesis" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" +) + +func main() { + key := genesis.EWOQKey + uri := primary.LocalAPIURI + kc := secp256k1fx.NewKeychain(key) + validationID := ids.FromStringOrPanic("9FAftNgNBrzHUMMApsSyV6RcFiL9UmCbvsCu28xdLV2mQ7CMo") + balance := uint64(2) + + ctx := context.Background() + + // MakeWallet fetches the available UTXOs owned by [kc] on the network that + // [uri] is hosting and registers [subnetID]. + walletSyncStartTime := time.Now() + wallet, err := primary.MakeWallet(ctx, &primary.WalletConfig{ + URI: uri, + AVAXKeychain: kc, + EthKeychain: kc, + }) + if err != nil { + log.Fatalf("failed to initialize wallet: %s\n", err) + } + log.Printf("synced wallet in %s\n", time.Since(walletSyncStartTime)) + + // Get the P-chain wallet + pWallet := wallet.P() + + increaseBalanceStartTime := time.Now() + increaseBalanceTx, err := pWallet.IssueIncreaseBalanceTx( + validationID, + balance, + ) + if err != nil { + log.Fatalf("failed to issue increase balance transaction: %s\n", err) + } + log.Printf("increased balance of validationID %s by %d with %s in %s\n", + validationID, + balance, + increaseBalanceTx.ID(), + time.Since(increaseBalanceStartTime), + ) +} From 3178ec0f830a791d52b0415d68f62c0f56b81a5d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 15:07:05 -0400 Subject: [PATCH 141/199] fix lint --- vms/platformvm/txs/executor/standard_tx_executor.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 1cced69c5c0f..f3ff2ab93557 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -49,6 +49,8 @@ var ( errEtnaUpgradeNotActive = errors.New("attempting to use an Etna-upgrade feature prior to activation") errTransformSubnetTxPostEtna = errors.New("TransformSubnetTx is not permitted post-Etna") errMaxNumActiveValidators = errors.New("already at the max number of active validators") + + errStateCorruption = errors.New("state corruption") ) type StandardTxExecutor struct { @@ -887,9 +889,10 @@ func (e *StandardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat accruedFees := e.State.GetAccruedFees() if sov.EndAccumulatedFee <= accruedFees { - // This should never happen as the validator should have been - // evicted. - return fmt.Errorf("validator has insufficient funds to cover accrued fees") + // This check should be unreachable. However, including it ensures + // that AVAX can't get minted out of thin air due to state + // corruption. + return fmt.Errorf("%w: validator should have already been disabled", errStateCorruption) } remainingBalance := sov.EndAccumulatedFee - accruedFees From d69548a1d4152eea6837739d4760b4a0c5e36ccd Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 15:20:06 -0400 Subject: [PATCH 142/199] bad tests --- .../block/executor/verifier_test.go | 3 + vms/platformvm/txs/executor/warp_verifier.go | 68 +++++++++---------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 2aa5e3ade0f6..247703abc427 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -132,6 +132,7 @@ func TestVerifierVisitProposalBlock(t *testing.T) { } blkTx := txsmock.NewUnsignedTx(ctrl) + blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.WarpVerifier{})).Return(nil).Times(1) blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.ProposalTxExecutor{})).Return(nil).Times(1) // We can't serialize [blkTx] because it isn't @@ -214,6 +215,7 @@ func TestVerifierVisitAtomicBlock(t *testing.T) { onAccept := state.NewMockDiff(ctrl) blkTx := txsmock.NewUnsignedTx(ctrl) inputs := set.Of(ids.GenerateTestID()) + blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.WarpVerifier{})).Return(nil).Times(1) blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.AtomicTxExecutor{})).DoAndReturn( func(e *executor.AtomicTxExecutor) error { e.OnAccept = onAccept @@ -306,6 +308,7 @@ func TestVerifierVisitStandardBlock(t *testing.T) { }, }, } + blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.WarpVerifier{})).Return(nil).Times(1) blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.StandardTxExecutor{})).DoAndReturn( func(e *executor.StandardTxExecutor) error { e.OnAccept = func() {} diff --git a/vms/platformvm/txs/executor/warp_verifier.go b/vms/platformvm/txs/executor/warp_verifier.go index f637d425a4cd..5c7430629f32 100644 --- a/vms/platformvm/txs/executor/warp_verifier.go +++ b/vms/platformvm/txs/executor/warp_verifier.go @@ -16,7 +16,7 @@ const ( WarpQuorumDenominator = 100 ) -var _ txs.Visitor = (*warpVerifier)(nil) +var _ txs.Visitor = (*WarpVerifier)(nil) // VerifyWarpMessages verifies all warp messages in the tx. If any of the warp // messages are invalid, an error is returned. @@ -27,105 +27,105 @@ func VerifyWarpMessages( pChainHeight uint64, tx txs.UnsignedTx, ) error { - return tx.Visit(&warpVerifier{ - ctx: ctx, - networkID: networkID, - validatorState: validatorState, - pChainHeight: pChainHeight, + return tx.Visit(&WarpVerifier{ + Context: ctx, + NetworkID: networkID, + ValidatorState: validatorState, + PChainHeight: pChainHeight, }) } -type warpVerifier struct { - ctx context.Context - networkID uint32 - validatorState validators.State - pChainHeight uint64 +type WarpVerifier struct { + Context context.Context + NetworkID uint32 + ValidatorState validators.State + PChainHeight uint64 } -func (*warpVerifier) AddValidatorTx(*txs.AddValidatorTx) error { +func (*WarpVerifier) AddValidatorTx(*txs.AddValidatorTx) error { return nil } -func (*warpVerifier) AddSubnetValidatorTx(*txs.AddSubnetValidatorTx) error { +func (*WarpVerifier) AddSubnetValidatorTx(*txs.AddSubnetValidatorTx) error { return nil } -func (*warpVerifier) AddDelegatorTx(*txs.AddDelegatorTx) error { +func (*WarpVerifier) AddDelegatorTx(*txs.AddDelegatorTx) error { return nil } -func (*warpVerifier) CreateChainTx(*txs.CreateChainTx) error { +func (*WarpVerifier) CreateChainTx(*txs.CreateChainTx) error { return nil } -func (*warpVerifier) CreateSubnetTx(*txs.CreateSubnetTx) error { +func (*WarpVerifier) CreateSubnetTx(*txs.CreateSubnetTx) error { return nil } -func (*warpVerifier) ImportTx(*txs.ImportTx) error { +func (*WarpVerifier) ImportTx(*txs.ImportTx) error { return nil } -func (*warpVerifier) ExportTx(*txs.ExportTx) error { +func (*WarpVerifier) ExportTx(*txs.ExportTx) error { return nil } -func (*warpVerifier) AdvanceTimeTx(*txs.AdvanceTimeTx) error { +func (*WarpVerifier) AdvanceTimeTx(*txs.AdvanceTimeTx) error { return nil } -func (*warpVerifier) RewardValidatorTx(*txs.RewardValidatorTx) error { +func (*WarpVerifier) RewardValidatorTx(*txs.RewardValidatorTx) error { return nil } -func (*warpVerifier) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { +func (*WarpVerifier) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { return nil } -func (*warpVerifier) TransformSubnetTx(*txs.TransformSubnetTx) error { +func (*WarpVerifier) TransformSubnetTx(*txs.TransformSubnetTx) error { return nil } -func (*warpVerifier) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { +func (*WarpVerifier) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { return nil } -func (*warpVerifier) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { +func (*WarpVerifier) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { return nil } -func (*warpVerifier) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { +func (*WarpVerifier) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { return nil } -func (*warpVerifier) BaseTx(*txs.BaseTx) error { +func (*WarpVerifier) BaseTx(*txs.BaseTx) error { return nil } -func (*warpVerifier) ConvertSubnetTx(*txs.ConvertSubnetTx) error { +func (*WarpVerifier) ConvertSubnetTx(*txs.ConvertSubnetTx) error { return nil } -func (w *warpVerifier) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetValidatorTx) error { +func (w *WarpVerifier) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetValidatorTx) error { return w.verify(tx.Message) } -func (w *warpVerifier) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidatorWeightTx) error { +func (w *WarpVerifier) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidatorWeightTx) error { return w.verify(tx.Message) } -func (w *warpVerifier) verify(message []byte) error { +func (w *WarpVerifier) verify(message []byte) error { msg, err := warp.ParseMessage(message) if err != nil { return err } return msg.Signature.Verify( - w.ctx, + w.Context, &msg.UnsignedMessage, - w.networkID, - w.validatorState, - w.pChainHeight, + w.NetworkID, + w.ValidatorState, + w.PChainHeight, WarpQuorumNumerator, WarpQuorumDenominator, ) From 1aa32c54340b2ecf08e63c5fd706e3392d0a3181 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 15:23:03 -0400 Subject: [PATCH 143/199] fix lint --- vms/platformvm/txs/executor/warp_verifier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/txs/executor/warp_verifier.go b/vms/platformvm/txs/executor/warp_verifier.go index 7572a3dfe849..71e6b03e798c 100644 --- a/vms/platformvm/txs/executor/warp_verifier.go +++ b/vms/platformvm/txs/executor/warp_verifier.go @@ -106,7 +106,7 @@ func (*WarpVerifier) ConvertSubnetTx(*txs.ConvertSubnetTx) error { return nil } -func (w *WarpVerifier) IncreaseBalanceTx(*txs.IncreaseBalanceTx) error { +func (*WarpVerifier) IncreaseBalanceTx(*txs.IncreaseBalanceTx) error { return nil } From a53154df36936d57d19f5766e5e32cb1b7c44c7a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 15:24:20 -0400 Subject: [PATCH 144/199] lint --- vms/platformvm/txs/executor/standard_tx_executor.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 35cc2dbefff9..d2167a91bdb0 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -1066,9 +1066,10 @@ func (e *StandardTxExecutor) DisableSubnetValidatorTx(tx *txs.DisableSubnetValid accruedFees := e.State.GetAccruedFees() if sov.EndAccumulatedFee <= accruedFees { - // This should never happen as the validator should have been - // evicted. - return fmt.Errorf("validator has insufficient funds to cover accrued fees") + // This check should be unreachable. However, including it ensures + // that AVAX can't get minted out of thin air due to state + // corruption. + return fmt.Errorf("%w: validator should have already been disabled", errStateCorruption) } remainingBalance := sov.EndAccumulatedFee - accruedFees From 795243c26568e4819c4b183244dc150cab1043ba Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 15:27:15 -0400 Subject: [PATCH 145/199] lint --- vms/platformvm/network/warp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/network/warp.go b/vms/platformvm/network/warp.go index d07a516a0f16..11e75943505f 100644 --- a/vms/platformvm/network/warp.go +++ b/vms/platformvm/network/warp.go @@ -1,4 +1,4 @@ -// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network From db94fad373072569d2f5fce5b8904642ebc4990c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 15:31:29 -0400 Subject: [PATCH 146/199] fix more tests --- snow/snowtest/context.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/snow/snowtest/context.go b/snow/snowtest/context.go index edeefe89c8ec..b1338cd4b635 100644 --- a/snow/snowtest/context.go +++ b/snow/snowtest/context.go @@ -65,6 +65,9 @@ func Context(tb testing.TB, chainID ids.ID) *snow.Context { require.NoError(aliaser.Alias(CChainID, CChainID.String())) validatorState := &validatorstest.State{ + GetMinimumHeightF: func(context.Context) (uint64, error) { + return 0, nil + }, GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { subnetID, ok := map[ids.ID]ids.ID{ constants.PlatformChainID: constants.PrimaryNetworkID, From 88d9ff02c8d48c6b5c00fb1ec17e0b11ef8e99aa Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 15:42:25 -0400 Subject: [PATCH 147/199] bad tests --- .../txs/executor/standard_tx_executor_test.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index afdd3ec14940..9854ce651157 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -1869,14 +1869,13 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { expectedErr: ErrRemovePermissionlessValidator, }, { - name: "tx has no credentials", + name: "can't find subnet", newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) - // Remove credentials - env.tx.Creds = nil env.state = state.NewMockDiff(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil) + env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(nil, database.ErrNotFound) cfg := &config.Config{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), @@ -1897,16 +1896,19 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { e.Bootstrapped.Set(true) return env.unsignedTx, e }, - expectedErr: errWrongNumberOfCredentials, + expectedErr: database.ErrNotFound, }, { - name: "can't find subnet", + name: "tx has no credentials", newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) + // Remove credentials + env.tx.Creds = nil env.state = state.NewMockDiff(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil) - env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(nil, database.ErrNotFound) + subnetOwner := fxmock.NewOwner(ctrl) + env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil).Times(1) cfg := &config.Config{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), @@ -1927,7 +1929,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { e.Bootstrapped.Set(true) return env.unsignedTx, e }, - expectedErr: database.ErrNotFound, + expectedErr: errWrongNumberOfCredentials, }, { name: "no permission to remove validator", @@ -2218,7 +2220,9 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { // Remove credentials env.tx.Creds = nil env.state = state.NewMockDiff(ctrl) + subnetOwner := fxmock.NewOwner(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() + env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil).Times(1) cfg := &config.Config{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), From 9ef28a194141206de144a9be4420fc0149799521 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 15:49:38 -0400 Subject: [PATCH 148/199] revert test change --- upgrade/upgrade.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/upgrade/upgrade.go b/upgrade/upgrade.go index 405adeffb5b7..21e404ffb3f4 100644 --- a/upgrade/upgrade.go +++ b/upgrade/upgrade.go @@ -72,7 +72,7 @@ var ( DurangoTime: InitiallyActiveTime, // Etna is left unactivated by default on local networks. It can be configured to // activate by overriding the activation time in the upgrade file. - EtnaTime: InitiallyActiveTime, + EtnaTime: UnscheduledActivationTime, } ErrInvalidUpgradeTimes = errors.New("invalid upgrade configuration") From 22f23b735e13ba4116611b6033d88beee33ea592 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 14:22:40 -0400 Subject: [PATCH 149/199] Allow different justification types --- proto/pb/platformvm/platformvm.pb.go | 282 +++++++++++++++++++++++++++ proto/platformvm/platformvm.proto | 21 ++ vms/platformvm/network/warp.go | 135 ++++++++++--- 3 files changed, 412 insertions(+), 26 deletions(-) create mode 100644 proto/pb/platformvm/platformvm.pb.go create mode 100644 proto/platformvm/platformvm.proto diff --git a/proto/pb/platformvm/platformvm.pb.go b/proto/pb/platformvm/platformvm.pb.go new file mode 100644 index 000000000000..10b40671a881 --- /dev/null +++ b/proto/pb/platformvm/platformvm.pb.go @@ -0,0 +1,282 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: platformvm/platformvm.proto + +package platformvm + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SubnetValidatorRegistrationJustification struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Preimage: + // + // *SubnetValidatorRegistrationJustification_ConvertSubnetTxData + // *SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage + Preimage isSubnetValidatorRegistrationJustification_Preimage `protobuf_oneof:"preimage"` + Filter []byte `protobuf:"bytes,3,opt,name=filter,proto3" json:"filter,omitempty"` +} + +func (x *SubnetValidatorRegistrationJustification) Reset() { + *x = SubnetValidatorRegistrationJustification{} + if protoimpl.UnsafeEnabled { + mi := &file_platformvm_platformvm_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubnetValidatorRegistrationJustification) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubnetValidatorRegistrationJustification) ProtoMessage() {} + +func (x *SubnetValidatorRegistrationJustification) ProtoReflect() protoreflect.Message { + mi := &file_platformvm_platformvm_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubnetValidatorRegistrationJustification.ProtoReflect.Descriptor instead. +func (*SubnetValidatorRegistrationJustification) Descriptor() ([]byte, []int) { + return file_platformvm_platformvm_proto_rawDescGZIP(), []int{0} +} + +func (m *SubnetValidatorRegistrationJustification) GetPreimage() isSubnetValidatorRegistrationJustification_Preimage { + if m != nil { + return m.Preimage + } + return nil +} + +func (x *SubnetValidatorRegistrationJustification) GetConvertSubnetTxData() *SubnetIDIndex { + if x, ok := x.GetPreimage().(*SubnetValidatorRegistrationJustification_ConvertSubnetTxData); ok { + return x.ConvertSubnetTxData + } + return nil +} + +func (x *SubnetValidatorRegistrationJustification) GetRegisterSubnetValidatorMessage() []byte { + if x, ok := x.GetPreimage().(*SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage); ok { + return x.RegisterSubnetValidatorMessage + } + return nil +} + +func (x *SubnetValidatorRegistrationJustification) GetFilter() []byte { + if x != nil { + return x.Filter + } + return nil +} + +type isSubnetValidatorRegistrationJustification_Preimage interface { + isSubnetValidatorRegistrationJustification_Preimage() +} + +type SubnetValidatorRegistrationJustification_ConvertSubnetTxData struct { + // Validator was added to the Subnet during the ConvertSubnetTx. + ConvertSubnetTxData *SubnetIDIndex `protobuf:"bytes,1,opt,name=convert_subnet_tx_data,json=convertSubnetTxData,proto3,oneof"` +} + +type SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage struct { + // Validator was registered to the Subnet after the ConvertSubnetTx. + // The SubnetValidator is being removed from the Subnet + RegisterSubnetValidatorMessage []byte `protobuf:"bytes,2,opt,name=register_subnet_validator_message,json=registerSubnetValidatorMessage,proto3,oneof"` +} + +func (*SubnetValidatorRegistrationJustification_ConvertSubnetTxData) isSubnetValidatorRegistrationJustification_Preimage() { +} + +func (*SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage) isSubnetValidatorRegistrationJustification_Preimage() { +} + +type SubnetIDIndex struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SubnetId []byte `protobuf:"bytes,1,opt,name=subnet_id,json=subnetId,proto3" json:"subnet_id,omitempty"` + Index uint32 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"` +} + +func (x *SubnetIDIndex) Reset() { + *x = SubnetIDIndex{} + if protoimpl.UnsafeEnabled { + mi := &file_platformvm_platformvm_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubnetIDIndex) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubnetIDIndex) ProtoMessage() {} + +func (x *SubnetIDIndex) ProtoReflect() protoreflect.Message { + mi := &file_platformvm_platformvm_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubnetIDIndex.ProtoReflect.Descriptor instead. +func (*SubnetIDIndex) Descriptor() ([]byte, []int) { + return file_platformvm_platformvm_proto_rawDescGZIP(), []int{1} +} + +func (x *SubnetIDIndex) GetSubnetId() []byte { + if x != nil { + return x.SubnetId + } + return nil +} + +func (x *SubnetIDIndex) GetIndex() uint32 { + if x != nil { + return x.Index + } + return 0 +} + +var File_platformvm_platformvm_proto protoreflect.FileDescriptor + +var file_platformvm_platformvm_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x76, 0x6d, 0x2f, 0x70, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x76, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x70, + 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x76, 0x6d, 0x22, 0xed, 0x01, 0x0a, 0x28, 0x53, 0x75, + 0x62, 0x6e, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x75, 0x73, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, + 0x74, 0x5f, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x74, 0x78, 0x5f, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, + 0x6d, 0x76, 0x6d, 0x2e, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x49, 0x44, 0x49, 0x6e, 0x64, 0x65, + 0x78, 0x48, 0x00, 0x52, 0x13, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x53, 0x75, 0x62, 0x6e, + 0x65, 0x74, 0x54, 0x78, 0x44, 0x61, 0x74, 0x61, 0x12, 0x4b, 0x0a, 0x21, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x1e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, + 0x75, 0x62, 0x6e, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x0a, 0x0a, + 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x42, 0x0a, 0x0d, 0x53, 0x75, 0x62, + 0x6e, 0x65, 0x74, 0x49, 0x44, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x75, + 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, + 0x75, 0x62, 0x6e, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x35, 0x5a, + 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x76, 0x61, 0x2d, + 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x61, 0x76, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x67, 0x6f, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x2f, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, + 0x72, 0x6d, 0x76, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_platformvm_platformvm_proto_rawDescOnce sync.Once + file_platformvm_platformvm_proto_rawDescData = file_platformvm_platformvm_proto_rawDesc +) + +func file_platformvm_platformvm_proto_rawDescGZIP() []byte { + file_platformvm_platformvm_proto_rawDescOnce.Do(func() { + file_platformvm_platformvm_proto_rawDescData = protoimpl.X.CompressGZIP(file_platformvm_platformvm_proto_rawDescData) + }) + return file_platformvm_platformvm_proto_rawDescData +} + +var file_platformvm_platformvm_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_platformvm_platformvm_proto_goTypes = []interface{}{ + (*SubnetValidatorRegistrationJustification)(nil), // 0: platformvm.SubnetValidatorRegistrationJustification + (*SubnetIDIndex)(nil), // 1: platformvm.SubnetIDIndex +} +var file_platformvm_platformvm_proto_depIdxs = []int32{ + 1, // 0: platformvm.SubnetValidatorRegistrationJustification.convert_subnet_tx_data:type_name -> platformvm.SubnetIDIndex + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_platformvm_platformvm_proto_init() } +func file_platformvm_platformvm_proto_init() { + if File_platformvm_platformvm_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_platformvm_platformvm_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubnetValidatorRegistrationJustification); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_platformvm_platformvm_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubnetIDIndex); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_platformvm_platformvm_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*SubnetValidatorRegistrationJustification_ConvertSubnetTxData)(nil), + (*SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_platformvm_platformvm_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_platformvm_platformvm_proto_goTypes, + DependencyIndexes: file_platformvm_platformvm_proto_depIdxs, + MessageInfos: file_platformvm_platformvm_proto_msgTypes, + }.Build() + File_platformvm_platformvm_proto = out.File + file_platformvm_platformvm_proto_rawDesc = nil + file_platformvm_platformvm_proto_goTypes = nil + file_platformvm_platformvm_proto_depIdxs = nil +} diff --git a/proto/platformvm/platformvm.proto b/proto/platformvm/platformvm.proto new file mode 100644 index 000000000000..a1e9adf129d1 --- /dev/null +++ b/proto/platformvm/platformvm.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package platformvm; + +option go_package = "github.com/ava-labs/avalanchego/proto/pb/platformvm"; + +message SubnetValidatorRegistrationJustification { + oneof preimage { + // Validator was added to the Subnet during the ConvertSubnetTx. + SubnetIDIndex convert_subnet_tx_data = 1; + // Validator was registered to the Subnet after the ConvertSubnetTx. + // The SubnetValidator is being removed from the Subnet + bytes register_subnet_validator_message = 2; + } + bytes filter = 3; +} + +message SubnetIDIndex { + bytes subnet_id = 1; + uint32 index = 2; +} diff --git a/vms/platformvm/network/warp.go b/vms/platformvm/network/warp.go index 11e75943505f..d8aabb17f134 100644 --- a/vms/platformvm/network/warp.go +++ b/vms/platformvm/network/warp.go @@ -9,9 +9,12 @@ import ( "math" "sync" + "google.golang.org/protobuf/proto" + "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p/acp118" + "github.com/ava-labs/avalanchego/proto/pb/platformvm" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/warp" @@ -129,9 +132,65 @@ func (s signatureRequestVerifier) verifySubnetConversion( func (s signatureRequestVerifier) verifySubnetValidatorRegistration( msg *message.SubnetValidatorRegistration, - justification []byte, + justificationBytes []byte, +) *common.AppError { + if msg.Registered { + return s.verifySubnetValidatorRegistered(msg.ValidationID) + } + + var justification platformvm.SubnetValidatorRegistrationJustification + if err := proto.Unmarshal(justificationBytes, &justification); err != nil { + return &common.AppError{ + Code: ErrFailedToParseJustification, + Message: "failed to parse justification: " + err.Error(), + } + } + + switch preimage := justification.GetPreimage().(type) { + case *platformvm.SubnetValidatorRegistrationJustification_ConvertSubnetTxData: + return s.verifySubnetValidatorNotCurrentlyRegistered(msg.ValidationID, preimage.ConvertSubnetTxData) + case *platformvm.SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage: + return s.verifySubnetValidatorCanNotValidate(msg.ValidationID, preimage.RegisterSubnetValidatorMessage) + default: + return &common.AppError{ + Code: ErrFailedToParseJustification, + Message: fmt.Sprintf("failed to parse justification: unsupported justification type %T", justification.Preimage), + } + } +} + +// verifySubnetValidatorCanNotValidate verifies that the validationID is +// currently a validator. +func (s signatureRequestVerifier) verifySubnetValidatorRegistered( + validationID ids.ID, +) *common.AppError { + s.stateLock.Lock() + defer s.stateLock.Unlock() + + // Verify that the validator exists + _, err := s.state.GetSubnetOnlyValidator(validationID) + if err == database.ErrNotFound { + return &common.AppError{ + Code: ErrValidationDoesNotExist, + Message: fmt.Sprintf("validation %q does not exist", validationID), + } + } + if err != nil { + return &common.AppError{ + Code: common.ErrUndefined.Code, + Message: "failed to get subnet only validator: " + err.Error(), + } + } + return nil +} + +// verifySubnetValidatorCanNotValidate verifies that the validationID is not +// currently a validator. +func (s signatureRequestVerifier) verifySubnetValidatorNotCurrentlyRegistered( + validationID ids.ID, + justification *platformvm.SubnetIDIndex, ) *common.AppError { - registerSubnetValidator, err := message.ParseRegisterSubnetValidator(justification) + subnetID, err := ids.ToID(justification.GetSubnetId()) if err != nil { return &common.AppError{ Code: ErrFailedToParseJustification, @@ -139,41 +198,65 @@ func (s signatureRequestVerifier) verifySubnetValidatorRegistration( } } - justificationID := registerSubnetValidator.ValidationID() - if msg.ValidationID != justificationID { + justificationID := subnetID.Append(justification.GetIndex()) + if validationID != justificationID { return &common.AppError{ Code: ErrMismatchedValidationID, - Message: fmt.Sprintf("validationID %q != justificationID %q", msg.ValidationID, justificationID), + Message: fmt.Sprintf("validationID %q != justificationID %q", validationID, justificationID), } } s.stateLock.Lock() defer s.stateLock.Unlock() - if msg.Registered { - // Verify that the validator exists - _, err := s.state.GetSubnetOnlyValidator(msg.ValidationID) - if err == database.ErrNotFound { - return &common.AppError{ - Code: ErrValidationDoesNotExist, - Message: fmt.Sprintf("validation %q does not exist", msg.ValidationID), - } - } - if err != nil { - return &common.AppError{ - Code: common.ErrUndefined.Code, - Message: "failed to get subnet only validator: " + err.Error(), - } + // Verify that the validator does not currently exist + _, err = s.state.GetSubnetOnlyValidator(validationID) + if err == nil { + return &common.AppError{ + Code: ErrValidationExists, + Message: fmt.Sprintf("validation %q exists", validationID), } - return nil } + if err != database.ErrNotFound { + return &common.AppError{ + Code: common.ErrUndefined.Code, + Message: "failed to lookup subnet only validator: " + err.Error(), + } + } + return nil +} + +// verifySubnetValidatorCanNotValidate verifies that the validationID does not +// currently and can never become a validator. +func (s signatureRequestVerifier) verifySubnetValidatorCanNotValidate( + validationID ids.ID, + justificationBytes []byte, +) *common.AppError { + justification, err := message.ParseRegisterSubnetValidator(justificationBytes) + if err != nil { + return &common.AppError{ + Code: ErrFailedToParseJustification, + Message: "failed to parse justification: " + err.Error(), + } + } + + justificationID := justification.ValidationID() + if validationID != justificationID { + return &common.AppError{ + Code: ErrMismatchedValidationID, + Message: fmt.Sprintf("validationID %q != justificationID %q", validationID, justificationID), + } + } + + s.stateLock.Lock() + defer s.stateLock.Unlock() // Verify that the validator does not and can never exists - _, err = s.state.GetSubnetOnlyValidator(msg.ValidationID) + _, err = s.state.GetSubnetOnlyValidator(validationID) if err == nil { return &common.AppError{ Code: ErrValidationExists, - Message: fmt.Sprintf("validation %q exists", msg.ValidationID), + Message: fmt.Sprintf("validation %q exists", validationID), } } if err != database.ErrNotFound { @@ -184,14 +267,14 @@ func (s signatureRequestVerifier) verifySubnetValidatorRegistration( } currentTimeUnix := uint64(s.state.GetTimestamp().Unix()) - if registerSubnetValidator.Expiry <= currentTimeUnix { + if justification.Expiry <= currentTimeUnix { // The validator is not registered and the expiry time has passed return nil } hasExpiry, err := s.state.HasExpiry(state.ExpiryEntry{ - Timestamp: registerSubnetValidator.Expiry, - ValidationID: msg.ValidationID, + Timestamp: justification.Expiry, + ValidationID: validationID, }) if err != nil { return &common.AppError{ @@ -202,7 +285,7 @@ func (s signatureRequestVerifier) verifySubnetValidatorRegistration( if !hasExpiry { return &common.AppError{ Code: ErrValidationCouldBeRegistered, - Message: fmt.Sprintf("validation %q can be registered until %d", msg.ValidationID, registerSubnetValidator.Expiry), + Message: fmt.Sprintf("validation %q can be registered until %d", validationID, justification.Expiry), } } From f17ff50aef8e9d25352931f4bf308ab3bfede3da Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 14:31:16 -0400 Subject: [PATCH 150/199] nits --- vms/platformvm/network/warp.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/vms/platformvm/network/warp.go b/vms/platformvm/network/warp.go index d8aabb17f134..c774c37e2bde 100644 --- a/vms/platformvm/network/warp.go +++ b/vms/platformvm/network/warp.go @@ -32,9 +32,12 @@ const ( ErrConversionDoesNotExist ErrMismatchedConversionID + ErrInvalidJustificationType + ErrFailedToParseSubnetID ErrMismatchedValidationID ErrValidationDoesNotExist ErrValidationExists + ErrFailedToParseRegisterSubnetValidator ErrValidationCouldBeRegistered ErrImpossibleNonce @@ -153,8 +156,8 @@ func (s signatureRequestVerifier) verifySubnetValidatorRegistration( return s.verifySubnetValidatorCanNotValidate(msg.ValidationID, preimage.RegisterSubnetValidatorMessage) default: return &common.AppError{ - Code: ErrFailedToParseJustification, - Message: fmt.Sprintf("failed to parse justification: unsupported justification type %T", justification.Preimage), + Code: ErrInvalidJustificationType, + Message: fmt.Sprintf("invalid justification type: %T", justification.Preimage), } } } @@ -193,8 +196,8 @@ func (s signatureRequestVerifier) verifySubnetValidatorNotCurrentlyRegistered( subnetID, err := ids.ToID(justification.GetSubnetId()) if err != nil { return &common.AppError{ - Code: ErrFailedToParseJustification, - Message: "failed to parse justification: " + err.Error(), + Code: ErrFailedToParseSubnetID, + Message: "failed to parse subnetID: " + err.Error(), } } @@ -235,8 +238,8 @@ func (s signatureRequestVerifier) verifySubnetValidatorCanNotValidate( justification, err := message.ParseRegisterSubnetValidator(justificationBytes) if err != nil { return &common.AppError{ - Code: ErrFailedToParseJustification, - Message: "failed to parse justification: " + err.Error(), + Code: ErrFailedToParseRegisterSubnetValidator, + Message: "failed to parse RegisterSubnetValidator justification: " + err.Error(), } } From ea0411790cd192465532d4ba8f210285eabf3e84 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 15:14:17 -0400 Subject: [PATCH 151/199] Disallow removal of last validator --- .../txs/executor/standard_tx_executor.go | 69 +++++++++++-------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index f3ff2ab93557..ff4ae2dcde1e 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -49,6 +49,7 @@ var ( errEtnaUpgradeNotActive = errors.New("attempting to use an Etna-upgrade feature prior to activation") errTransformSubnetTxPostEtna = errors.New("TransformSubnetTx is not permitted post-Etna") errMaxNumActiveValidators = errors.New("already at the max number of active validators") + errRemovingLastValidator = errors.New("attempting to remove the last SoV from a converted subnet") errStateCorruption = errors.New("state corruption") ) @@ -879,40 +880,52 @@ func (e *StandardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat } txID := e.Tx.ID() - if msg.Weight == 0 && sov.EndAccumulatedFee != 0 { - // If we are removing an active validator, we need to refund the - // remaining balance. - var remainingBalanceOwner message.PChainOwner - if _, err := txs.Codec.Unmarshal(sov.RemainingBalanceOwner, &remainingBalanceOwner); err != nil { + + // We are removing the validator + if msg.Weight == 0 { + weight, err := e.State.WeightOfSubnetOnlyValidators(sov.SubnetID) + if err != nil { return err } - - accruedFees := e.State.GetAccruedFees() - if sov.EndAccumulatedFee <= accruedFees { - // This check should be unreachable. However, including it ensures - // that AVAX can't get minted out of thin air due to state - // corruption. - return fmt.Errorf("%w: validator should have already been disabled", errStateCorruption) + if weight == sov.Weight { + return errRemovingLastValidator } - remainingBalance := sov.EndAccumulatedFee - accruedFees - utxo := &avax.UTXO{ - UTXOID: avax.UTXOID{ - TxID: txID, - OutputIndex: uint32(len(tx.Outs)), - }, - Asset: avax.Asset{ - ID: e.Ctx.AVAXAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: remainingBalance, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: remainingBalanceOwner.Threshold, - Addrs: remainingBalanceOwner.Addresses, + // The validator is currently active, we need to refund the remaining + // balance. + if sov.EndAccumulatedFee != 0 { + var remainingBalanceOwner message.PChainOwner + if _, err := txs.Codec.Unmarshal(sov.RemainingBalanceOwner, &remainingBalanceOwner); err != nil { + return err + } + + accruedFees := e.State.GetAccruedFees() + if sov.EndAccumulatedFee <= accruedFees { + // This check should be unreachable. However, including it ensures + // that AVAX can't get minted out of thin air due to state + // corruption. + return fmt.Errorf("%w: validator should have already been disabled", errStateCorruption) + } + remainingBalance := sov.EndAccumulatedFee - accruedFees + + utxo := &avax.UTXO{ + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: uint32(len(tx.Outs)), }, - }, + Asset: avax.Asset{ + ID: e.Ctx.AVAXAssetID, + }, + Out: &secp256k1fx.TransferOutput{ + Amt: remainingBalance, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: remainingBalanceOwner.Threshold, + Addrs: remainingBalanceOwner.Addresses, + }, + }, + } + e.State.AddUTXO(utxo) } - e.State.AddUTXO(utxo) } // If the weight is being set to 0, the validator is being removed and the From 5533b8c657de2253046407394b08909bc9970933 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 16:08:31 -0400 Subject: [PATCH 152/199] Update examples --- .../primary/examples/convert-subnet/main.go | 2 +- .../register-subnet-validator/main.go | 2 +- .../set-subnet-validator-weight/main.go | 6 +- .../examples/sign-subnet-conversion/main.go | 2 +- .../main.go | 86 +---------- .../main.go | 137 ++++++++++++++++++ .../main.go | 107 ++++++++------ 7 files changed, 207 insertions(+), 135 deletions(-) create mode 100644 wallet/subnet/primary/examples/sign-subnet-validator-removal-genesis/main.go rename wallet/subnet/primary/examples/{sign-subnet-validator-removal => sign-subnet-validator-removal-registration}/main.go (73%) diff --git a/wallet/subnet/primary/examples/convert-subnet/main.go b/wallet/subnet/primary/examples/convert-subnet/main.go index b7133488f6ef..c306aca3549d 100644 --- a/wallet/subnet/primary/examples/convert-subnet/main.go +++ b/wallet/subnet/primary/examples/convert-subnet/main.go @@ -24,7 +24,7 @@ func main() { uri := "http://localhost:9700" kc := secp256k1fx.NewKeychain(key) subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") - chainID := ids.FromStringOrPanic("E8nTR9TtRwfkS7XFjTYUYHENQ91mkPMtDUwwCeu7rNgBBtkqu") + chainID := ids.FromStringOrPanic("4R1dLAnG45P3rbdJB2dWuKdVRZF3dLMKgfJ8J6wKSQvYFVUhb") addressHex := "" weight := units.Schmeckle diff --git a/wallet/subnet/primary/examples/register-subnet-validator/main.go b/wallet/subnet/primary/examples/register-subnet-validator/main.go index 05d51aa1719b..b0d1b0a2c653 100644 --- a/wallet/subnet/primary/examples/register-subnet-validator/main.go +++ b/wallet/subnet/primary/examples/register-subnet-validator/main.go @@ -29,7 +29,7 @@ func main() { uri := "http://localhost:9710" kc := secp256k1fx.NewKeychain(key) subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") - chainID := ids.FromStringOrPanic("2BMFrJ9xeh5JdwZEx6uuFcjfZC2SV2hdbMT8ee5HrvjtfJb5br") + chainID := ids.FromStringOrPanic("4R1dLAnG45P3rbdJB2dWuKdVRZF3dLMKgfJ8J6wKSQvYFVUhb") addressHex := "" weight := uint64(1) diff --git a/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go b/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go index 35e7ce264104..aacf99ba1f9e 100644 --- a/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go +++ b/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go @@ -26,11 +26,11 @@ func main() { key := genesis.EWOQKey uri := primary.LocalAPIURI kc := secp256k1fx.NewKeychain(key) - chainID := ids.FromStringOrPanic("2BMFrJ9xeh5JdwZEx6uuFcjfZC2SV2hdbMT8ee5HrvjtfJb5br") + chainID := ids.FromStringOrPanic("4R1dLAnG45P3rbdJB2dWuKdVRZF3dLMKgfJ8J6wKSQvYFVUhb") addressHex := "" - validationID := ids.FromStringOrPanic("2Y3ZZZXxpzm46geqVuqFXeSFVbeKihgrfeXRDaiF4ds6R2N8M5") + validationID := ids.FromStringOrPanic("9FAftNgNBrzHUMMApsSyV6RcFiL9UmCbvsCu28xdLV2mQ7CMo") nonce := uint64(1) - weight := uint64(2) + weight := uint64(0) address, err := hex.DecodeString(addressHex) if err != nil { diff --git a/wallet/subnet/primary/examples/sign-subnet-conversion/main.go b/wallet/subnet/primary/examples/sign-subnet-conversion/main.go index 87f159855853..1d3fdd7a47de 100644 --- a/wallet/subnet/primary/examples/sign-subnet-conversion/main.go +++ b/wallet/subnet/primary/examples/sign-subnet-conversion/main.go @@ -32,7 +32,7 @@ import ( func main() { uri := primary.LocalAPIURI subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") - conversionID := ids.FromStringOrPanic("d84X4xQeXkAhnLQi2BqDyG6AEGbdJVGJCwx6a4c3UnBhD9vpZ") + conversionID := ids.FromStringOrPanic("28tfqwucuoH7oWxmVYDVQ2C1ehdYecF5mzwNmX2t1dTu1S5vHE") infoClient := info.NewClient(uri) networkID, err := infoClient.GetNetworkID(context.Background()) if err != nil { diff --git a/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go b/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go index 6506d2477c2c..71524515ab9e 100644 --- a/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go +++ b/wallet/subnet/primary/examples/sign-subnet-validator-registration/main.go @@ -5,7 +5,6 @@ package main import ( "context" - "encoding/json" "log" "net/netip" "time" @@ -14,6 +13,7 @@ import ( "google.golang.org/protobuf/proto" "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/network/peer" "github.com/ava-labs/avalanchego/proto/pb/sdk" @@ -29,90 +29,15 @@ import ( warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" ) -var registerSubnetValidatorJSON = []byte(`{ - "subnetID": "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof", - "nodeID": "0xb628ee3952a5de80fadd31ab030a67189edb1410", - "blsPublicKey": [ - 143, - 167, - 255, - 128, - 221, - 92, - 126, - 190, - 134, - 189, - 157, - 166, - 6, - 55, - 92, - 125, - 223, - 231, - 71, - 85, - 122, - 110, - 110, - 49, - 215, - 14, - 1, - 226, - 146, - 140, - 73, - 75, - 113, - 163, - 138, - 158, - 34, - 207, - 99, - 36, - 137, - 55, - 191, - 28, - 186, - 24, - 49, - 199 - ], - "expiry": 1727975059, - "remainingBalanceOwner": { - "threshold": 0, - "addresses": null - }, - "disableOwner": { - "threshold": 0, - "addresses": null - }, - "weight": 1 -}`) - func main() { uri := primary.LocalAPIURI + validationID := ids.FromStringOrPanic("2DWCCiYb7xRTRHeKybkLY5ygRhZ1CWhtHgLuUCJBxktRnUYdCT") infoClient := info.NewClient(uri) networkID, err := infoClient.GetNetworkID(context.Background()) if err != nil { log.Fatalf("failed to fetch network ID: %s\n", err) } - var registerSubnetValidator warpmessage.RegisterSubnetValidator - err = json.Unmarshal(registerSubnetValidatorJSON, ®isterSubnetValidator) - if err != nil { - log.Fatalf("failed to unmarshal RegisterSubnetValidator message: %s\n", err) - } - err = warpmessage.Initialize(®isterSubnetValidator) - if err != nil { - log.Fatalf("failed to initialize RegisterSubnetValidator message: %s\n", err) - } - - validationID := registerSubnetValidator.ValidationID() subnetValidatorRegistration, err := warpmessage.NewSubnetValidatorRegistration( validationID, true, @@ -153,7 +78,7 @@ func main() { log.Fatalf("failed to start peer: %s\n", err) } - mesageBuilder, err := p2pmessage.NewCreator( + messageBuilder, err := p2pmessage.NewCreator( logging.NoLog{}, prometheus.NewRegistry(), compression.TypeZstd, @@ -164,14 +89,13 @@ func main() { } appRequestPayload, err := proto.Marshal(&sdk.SignatureRequest{ - Message: unsignedWarp.Bytes(), - Justification: registerSubnetValidator.Bytes(), + Message: unsignedWarp.Bytes(), }) if err != nil { log.Fatalf("failed to marshal SignatureRequest: %s\n", err) } - appRequest, err := mesageBuilder.AppRequest( + appRequest, err := messageBuilder.AppRequest( constants.PlatformChainID, 0, time.Hour, diff --git a/wallet/subnet/primary/examples/sign-subnet-validator-removal-genesis/main.go b/wallet/subnet/primary/examples/sign-subnet-validator-removal-genesis/main.go new file mode 100644 index 000000000000..7b4eb57e0d69 --- /dev/null +++ b/wallet/subnet/primary/examples/sign-subnet-validator-removal-genesis/main.go @@ -0,0 +1,137 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "log" + "net/netip" + "time" + + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/protobuf/proto" + + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/peer" + "github.com/ava-labs/avalanchego/proto/pb/platformvm" + "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/snow/networking/router" + "github.com/ava-labs/avalanchego/utils/compression" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + + p2pmessage "github.com/ava-labs/avalanchego/message" + warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" +) + +func main() { + uri := primary.LocalAPIURI + subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") + validationIndex := uint32(0) + infoClient := info.NewClient(uri) + networkID, err := infoClient.GetNetworkID(context.Background()) + if err != nil { + log.Fatalf("failed to fetch network ID: %s\n", err) + } + + validationID := subnetID.Append(validationIndex) + subnetValidatorRegistration, err := warpmessage.NewSubnetValidatorRegistration( + validationID, + false, + ) + if err != nil { + log.Fatalf("failed to create SubnetValidatorRegistration message: %s\n", err) + } + + addressedCall, err := payload.NewAddressedCall( + nil, + subnetValidatorRegistration.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create AddressedCall message: %s\n", err) + } + + unsignedWarp, err := warp.NewUnsignedMessage( + networkID, + constants.PlatformChainID, + addressedCall.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create unsigned Warp message: %s\n", err) + } + + justification := platformvm.SubnetValidatorRegistrationJustification{ + Preimage: &platformvm.SubnetValidatorRegistrationJustification_ConvertSubnetTxData{ + ConvertSubnetTxData: &platformvm.SubnetIDIndex{ + SubnetId: subnetID[:], + Index: validationIndex, + }, + }, + } + justificationBytes, err := proto.Marshal(&justification) + if err != nil { + log.Fatalf("failed to create justification: %s\n", err) + } + + p, err := peer.StartTestPeer( + context.Background(), + netip.AddrPortFrom( + netip.AddrFrom4([4]byte{127, 0, 0, 1}), + 9651, + ), + networkID, + router.InboundHandlerFunc(func(_ context.Context, msg p2pmessage.InboundMessage) { + log.Printf("received %s: %s", msg.Op(), msg.Message()) + }), + ) + if err != nil { + log.Fatalf("failed to start peer: %s\n", err) + } + + messageBuilder, err := p2pmessage.NewCreator( + logging.NoLog{}, + prometheus.NewRegistry(), + compression.TypeZstd, + time.Hour, + ) + if err != nil { + log.Fatalf("failed to create message builder: %s\n", err) + } + + appRequestPayload, err := proto.Marshal(&sdk.SignatureRequest{ + Message: unsignedWarp.Bytes(), + Justification: justificationBytes, + }) + if err != nil { + log.Fatalf("failed to marshal SignatureRequest: %s\n", err) + } + + appRequest, err := messageBuilder.AppRequest( + constants.PlatformChainID, + 0, + time.Hour, + p2p.PrefixMessage( + p2p.ProtocolPrefix(p2p.SignatureRequestHandlerID), + appRequestPayload, + ), + ) + if err != nil { + log.Fatalf("failed to create AppRequest: %s\n", err) + } + + p.Send(context.Background(), appRequest) + + time.Sleep(5 * time.Second) + + p.StartClose() + err = p.AwaitClosed(context.Background()) + if err != nil { + log.Fatalf("failed to close peer: %s\n", err) + } +} diff --git a/wallet/subnet/primary/examples/sign-subnet-validator-removal/main.go b/wallet/subnet/primary/examples/sign-subnet-validator-removal-registration/main.go similarity index 73% rename from wallet/subnet/primary/examples/sign-subnet-validator-removal/main.go rename to wallet/subnet/primary/examples/sign-subnet-validator-removal-registration/main.go index 02ae39abef7d..aab951eadab6 100644 --- a/wallet/subnet/primary/examples/sign-subnet-validator-removal/main.go +++ b/wallet/subnet/primary/examples/sign-subnet-validator-removal-registration/main.go @@ -16,6 +16,7 @@ import ( "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/network/peer" + "github.com/ava-labs/avalanchego/proto/pb/platformvm" "github.com/ava-labs/avalanchego/proto/pb/sdk" "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/utils/compression" @@ -31,58 +32,58 @@ import ( var registerSubnetValidatorJSON = []byte(`{ "subnetID": "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof", - "nodeID": "0xb628ee3952a5de80fadd31ab030a67189edb1410", + "nodeID": "0x550f3c8f2ebd89e6a69adca196bea38a1b4d65bc", "blsPublicKey": [ - 143, - 167, - 255, - 128, - 221, - 92, - 126, - 190, - 134, - 189, - 157, - 166, + 178, + 119, + 51, + 152, + 247, + 239, + 52, + 16, + 89, + 246, 6, - 55, - 92, - 125, - 223, - 231, - 71, - 85, - 122, - 110, - 110, - 49, - 215, - 14, - 1, - 226, - 146, - 140, - 73, + 11, + 76, + 81, + 114, + 139, + 141, + 251, + 127, + 202, + 205, + 177, + 62, 75, - 113, - 163, - 138, - 158, - 34, + 152, 207, - 99, - 36, - 137, - 55, - 191, - 28, - 186, - 24, - 49, - 199 + 170, + 120, + 86, + 213, + 226, + 226, + 104, + 135, + 245, + 231, + 226, + 223, + 64, + 19, + 242, + 246, + 227, + 12, + 223, + 23, + 193, + 219 ], - "expiry": 1727975059, + "expiry": 1728331617, "remainingBalanceOwner": { "threshold": 0, "addresses": null @@ -138,6 +139,16 @@ func main() { log.Fatalf("failed to create unsigned Warp message: %s\n", err) } + justification := platformvm.SubnetValidatorRegistrationJustification{ + Preimage: &platformvm.SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage{ + RegisterSubnetValidatorMessage: registerSubnetValidator.Bytes(), + }, + } + justificationBytes, err := proto.Marshal(&justification) + if err != nil { + log.Fatalf("failed to create justification: %s\n", err) + } + p, err := peer.StartTestPeer( context.Background(), netip.AddrPortFrom( @@ -165,7 +176,7 @@ func main() { appRequestPayload, err := proto.Marshal(&sdk.SignatureRequest{ Message: unsignedWarp.Bytes(), - Justification: registerSubnetValidator.Bytes(), + Justification: justificationBytes, }) if err != nil { log.Fatalf("failed to marshal SignatureRequest: %s\n", err) From 237f590866ae225f306482bf004126bc24560f38 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 16:29:41 -0400 Subject: [PATCH 153/199] Allow xsvm to sign arbitrary warp messages --- vms/example/xsvm/vm.go | 44 ++++++++++++++++++++++++++++------------ vms/example/xsvm/warp.go | 18 ++++++++++++++++ 2 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 vms/example/xsvm/warp.go diff --git a/vms/example/xsvm/vm.go b/vms/example/xsvm/vm.go index 526fc47c499d..967ee7dd6a03 100644 --- a/vms/example/xsvm/vm.go +++ b/vms/example/xsvm/vm.go @@ -9,17 +9,19 @@ import ( "net/http" "github.com/gorilla/rpc/v2" + "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/versiondb" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/p2p/acp118" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/json" - "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms/example/xsvm/api" "github.com/ava-labs/avalanchego/vms/example/xsvm/builder" "github.com/ava-labs/avalanchego/vms/example/xsvm/chain" @@ -37,7 +39,7 @@ var ( ) type VM struct { - common.AppHandler + *p2p.Network chainContext *snow.Context db database.Database @@ -57,14 +59,38 @@ func (vm *VM) Initialize( _ []byte, engineChan chan<- common.Message, _ []*common.Fx, - _ common.AppSender, + appSender common.AppSender, ) error { - vm.AppHandler = common.NewNoOpAppHandler(chainContext.Log) - chainContext.Log.Info("initializing xsvm", zap.Stringer("version", Version), ) + metrics := prometheus.NewRegistry() + err := chainContext.Metrics.Register("p2p", metrics) + if err != nil { + return err + } + + vm.Network, err = p2p.NewNetwork( + chainContext.Log, + appSender, + metrics, + "", + ) + if err != nil { + return err + } + + // Allow signing of all warp messages. This is not typically safe, but is + // allowed for this example. + acp118Handler := acp118.NewHandler( + acp118Verifier{}, + chainContext.WarpSigner, + ) + if err := vm.Network.AddHandler(p2p.SignatureRequestHandlerID, acp118Handler); err != nil { + return err + } + vm.chainContext = chainContext vm.db = db g, err := genesis.Parse(genesisBytes) @@ -132,14 +158,6 @@ func (*VM) HealthCheck(context.Context) (interface{}, error) { return http.StatusOK, nil } -func (*VM) Connected(context.Context, ids.NodeID, *version.Application) error { - return nil -} - -func (*VM) Disconnected(context.Context, ids.NodeID) error { - return nil -} - func (vm *VM) GetBlock(_ context.Context, blkID ids.ID) (snowman.Block, error) { return vm.chain.GetBlock(blkID) } diff --git a/vms/example/xsvm/warp.go b/vms/example/xsvm/warp.go new file mode 100644 index 000000000000..224945abd88a --- /dev/null +++ b/vms/example/xsvm/warp.go @@ -0,0 +1,18 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package xsvm + +import ( + "context" + + "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" +) + +// acp118Verifier allows signing all warp messages +type acp118Verifier struct{} + +func (acp118Verifier) Verify(context.Context, *warp.UnsignedMessage, []byte) *common.AppError { + return nil +} From 71b997c28c81e8f68371799f6302012b47ab8e7b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 18:34:52 -0400 Subject: [PATCH 154/199] Expand e2e test --- genesis/genesis_local.go | 6 +- tests/e2e/p/permissionless_layer_one.go | 81 ++++++++++++++++++++----- 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/genesis/genesis_local.go b/genesis/genesis_local.go index aefdbf2c25be..5834a366ed2b 100644 --- a/genesis/genesis_local.go +++ b/genesis/genesis_local.go @@ -61,10 +61,10 @@ var ( gas.Compute: 1, }, MaxCapacity: 1_000_000, - MaxPerSecond: 1_000, - TargetPerSecond: 500, + MaxPerSecond: 250_000, + TargetPerSecond: 100_000, MinPrice: 1, - ExcessConversionConstant: 5_000, + ExcessConversionConstant: 1_000_000, }, ValidatorFeeCapacity: 20_000, ValidatorFeeConfig: validatorfee.Config{ diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index e3eab63ebffc..64ee1da7aa8f 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -4,18 +4,24 @@ package p import ( + "math" "time" "github.com/onsi/ginkgo/v2" "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/tests/fixture/e2e" + "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/vms/example/xsvm/genesis" "github.com/ava-labs/avalanchego/vms/platformvm" - "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -53,6 +59,20 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }, } + genesisKey, err := secp256k1.NewPrivateKey() + require.NoError(err) + + genesisBytes, err := genesis.Codec.Marshal(genesis.CodecVersion, &genesis.Genesis{ + Timestamp: time.Now().Unix(), + Allocations: []genesis.Allocation{ + { + Address: genesisKey.Address(), + Balance: math.MaxUint64, + }, + }, + }) + require.NoError(err) + tc.By("issuing a CreateSubnetTx") subnetTx, err := pWallet.IssueCreateSubnetTx( owner, @@ -66,7 +86,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { res, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) require.NoError(err) - require.Equal( platformvm.GetSubnetClientResponse{ IsPermissioned: true, @@ -78,17 +97,34 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { res, ) - const weight = 100 - var ( - chainID = ids.GenerateTestID() - address = []byte{'a', 'd', 'd', 'r', 'e', 's', 's'} - nodeID = ids.GenerateTestNodeID() + tc.By("issuing a CreateChainTx") + chainTx, err := pWallet.IssueCreateChainTx( + subnetID, + genesisBytes, + constants.XSVMID, + nil, + "No Permissions", + tc.WithDefaultContext(), ) + require.NoError(err) + + tc.By("creating an ephemeral node") + subnetGenesisNode := e2e.AddEphemeralNode(tc, env.GetNetwork(), tmpnet.FlagsMap{ + config.TrackSubnetsKey: subnetID.String(), + }) + + subnetGenesisNodeInfoAPI := info.NewClient(subnetGenesisNode.URI) + nodeID, nodePoP, err := subnetGenesisNodeInfoAPI.GetNodeID(tc.DefaultContext()) + require.NoError(err) - sk, err := bls.NewSecretKey() + nodePK, err := bls.PublicKeyFromCompressedBytes(nodePoP.PublicKey[:]) require.NoError(err) - pop := signer.NewProofOfPossession(sk) + const weight = 100 + var ( + chainID = chainTx.ID() + address = []byte{} + ) tc.By("issuing a ConvertSubnetTx") _, err = pWallet.IssueConvertSubnetTx( subnetID, @@ -96,9 +132,10 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { address, []*txs.ConvertSubnetValidator{ { - NodeID: nodeID.Bytes(), - Weight: weight, - Signer: *pop, + NodeID: nodeID.Bytes(), + Weight: weight, + Balance: units.Avax, + Signer: *nodePoP, }, }, tc.WithDefaultContext(), @@ -112,7 +149,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { Validators: []message.SubnetConversionValidatorData{ { NodeID: nodeID.Bytes(), - BLSPublicKey: pop.PublicKey, + BLSPublicKey: nodePoP.PublicKey, Weight: weight, }, }, @@ -122,7 +159,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("verifying the Permissioned Subnet was converted to a Permissionless L1") res, err = pClient.GetSubnet(tc.DefaultContext(), subnetID) require.NoError(err) - require.Equal( platformvm.GetSubnetClientResponse{ IsPermissioned: false, @@ -136,5 +172,22 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }, res, ) + + tc.By("verifying the Permissionless L1 reports the correct validator set") + height, err := pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) + + subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) + require.NoError(err) + require.Equal( + map[ids.NodeID]*validators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: nodePK, + Weight: weight, + }, + }, + subnetValidators, + ) }) }) From 567b00fb2efae053e40a3e39f9846c3d219efb1e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 18:47:03 -0400 Subject: [PATCH 155/199] fix test --- vms/platformvm/block/executor/verifier_test.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 2aa5e3ade0f6..e11d4fc42e96 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -1139,6 +1139,11 @@ func TestBlockExecutionWithComplexity(t *testing.T) { blockGas, err := blockComplexity.ToGas(verifier.txExecutorBackend.Config.DynamicFeeConfig.Weights) require.NoError(t, err) + initialFeeState := gas.State{} + must := func(s gas.State, err error) gas.State { + require.NoError(t, err) + return s + } tests := []struct { name string timestamp time.Time @@ -1153,10 +1158,12 @@ func TestBlockExecutionWithComplexity(t *testing.T) { { name: "updates fee state", timestamp: genesistest.DefaultValidatorStartTime.Add(10 * time.Second), - expectedFeeState: gas.State{ - Capacity: gas.Gas(0).AddPerSecond(verifier.txExecutorBackend.Config.DynamicFeeConfig.MaxPerSecond, 10) - blockGas, - Excess: blockGas, - }, + expectedFeeState: must(initialFeeState.AdvanceTime( + verifier.txExecutorBackend.Config.DynamicFeeConfig.MaxCapacity, + verifier.txExecutorBackend.Config.DynamicFeeConfig.MaxPerSecond, + verifier.txExecutorBackend.Config.DynamicFeeConfig.TargetPerSecond, + 10, + ).ConsumeGas(blockGas)), }, } for _, test := range tests { From 2373cf6e33a7d12550de76ab9a02b704a23c0ebe Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 19:12:24 -0400 Subject: [PATCH 156/199] nit --- tests/e2e/p/permissionless_layer_one.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 64ee1da7aa8f..1d21163a841d 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -4,6 +4,7 @@ package p import ( + "context" "math" "time" @@ -13,9 +14,13 @@ import ( "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/message" + "github.com/ava-labs/avalanchego/network/peer" + "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/tests/fixture/e2e" "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" + "github.com/ava-labs/avalanchego/utils/buffer" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" @@ -23,7 +28,6 @@ import ( "github.com/ava-labs/avalanchego/vms/example/xsvm/genesis" "github.com/ava-labs/avalanchego/vms/platformvm" "github.com/ava-labs/avalanchego/vms/platformvm/txs" - "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) @@ -189,5 +193,23 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }, subnetValidators, ) + + tc.By("connecting to the Permissionless L1 genesis validator") + genesisPeerMessages := buffer.NewUnboundedBlockingDeque[message.InboundMessage](1) + genesisPeer, err := peer.StartTestPeer( + tc.DefaultContext(), + subnetGenesisNode.StakingAddress, + env.GetNetwork().NetworkID, + router.InboundHandlerFunc(func(_ context.Context, m message.InboundMessage) { + genesisPeerMessages.PushRight(m) + }), + ) + require.NoError(err) + defer func() { + genesisPeer.StartClose() + require.NoError(genesisPeer.AwaitClosed(tc.DefaultContext())) + }() + + e2e.WaitForHealthy(tc, subnetGenesisNode) }) }) From 4ba542a06c68e88c4ed600b7b6cd9260ff552864 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 20:36:42 -0400 Subject: [PATCH 157/199] wip e2e test --- tests/e2e/p/permissionless_layer_one.go | 259 +++++++++++++++++++++--- 1 file changed, 229 insertions(+), 30 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 1d21163a841d..03413473f7c4 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -6,29 +6,42 @@ package p import ( "context" "math" + "slices" "time" "github.com/onsi/ginkgo/v2" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/message" "github.com/ava-labs/avalanchego/network/peer" + "github.com/ava-labs/avalanchego/proto/pb/sdk" "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/tests/fixture/e2e" "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" + "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/buffer" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/example/xsvm/genesis" "github.com/ava-labs/avalanchego/vms/platformvm" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/avalanchego/vms/secp256k1fx" + + p2pmessage "github.com/ava-labs/avalanchego/message" + p2psdk "github.com/ava-labs/avalanchego/network/p2p" + p2ppb "github.com/ava-labs/avalanchego/proto/pb/p2p" + warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" ) var _ = e2e.DescribePChain("[Permissionless L1]", func() { @@ -112,19 +125,37 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ) require.NoError(err) - tc.By("creating an ephemeral node") + tc.By("creating the genesis validator") subnetGenesisNode := e2e.AddEphemeralNode(tc, env.GetNetwork(), tmpnet.FlagsMap{ config.TrackSubnetsKey: subnetID.String(), }) - subnetGenesisNodeInfoAPI := info.NewClient(subnetGenesisNode.URI) - nodeID, nodePoP, err := subnetGenesisNodeInfoAPI.GetNodeID(tc.DefaultContext()) + genesisNodePoP, err := subnetGenesisNode.GetProofOfPossession() require.NoError(err) - nodePK, err := bls.PublicKeyFromCompressedBytes(nodePoP.PublicKey[:]) + genesisNodePK, err := bls.PublicKeyFromCompressedBytes(genesisNodePoP.PublicKey[:]) require.NoError(err) - const weight = 100 + tc.By("connecting to the genesis validator") + var ( + networkID = env.GetNetwork().NetworkID + genesisPeerMessages = buffer.NewUnboundedBlockingDeque[p2pmessage.InboundMessage](1) + ) + genesisPeer, err := peer.StartTestPeer( + tc.DefaultContext(), + subnetGenesisNode.StakingAddress, + networkID, + router.InboundHandlerFunc(func(_ context.Context, m p2pmessage.InboundMessage) { + genesisPeerMessages.PushRight(m) + }), + ) + require.NoError(err) + defer func() { + genesisPeer.StartClose() + require.NoError(genesisPeer.AwaitClosed(tc.DefaultContext())) + }() + + const genesisWeight = 100 var ( chainID = chainTx.ID() address = []byte{} @@ -136,25 +167,25 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { address, []*txs.ConvertSubnetValidator{ { - NodeID: nodeID.Bytes(), - Weight: weight, + NodeID: subnetGenesisNode.NodeID.Bytes(), + Weight: genesisWeight, Balance: units.Avax, - Signer: *nodePoP, + Signer: *genesisNodePoP, }, }, tc.WithDefaultContext(), ) require.NoError(err) - expectedConversionID, err := message.SubnetConversionID(message.SubnetConversionData{ + expectedConversionID, err := warpmessage.SubnetConversionID(warpmessage.SubnetConversionData{ SubnetID: subnetID, ManagerChainID: chainID, ManagerAddress: address, - Validators: []message.SubnetConversionValidatorData{ + Validators: []warpmessage.SubnetConversionValidatorData{ { - NodeID: nodeID.Bytes(), - BLSPublicKey: nodePoP.PublicKey, - Weight: weight, + NodeID: subnetGenesisNode.NodeID.Bytes(), + BLSPublicKey: genesisNodePoP.PublicKey, + Weight: genesisWeight, }, }, }) @@ -187,29 +218,197 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { map[ids.NodeID]*validators.GetValidatorOutput{ subnetGenesisNode.NodeID: { NodeID: subnetGenesisNode.NodeID, - PublicKey: nodePK, - Weight: weight, + PublicKey: genesisNodePK, + Weight: genesisWeight, }, }, subnetValidators, ) - tc.By("connecting to the Permissionless L1 genesis validator") - genesisPeerMessages := buffer.NewUnboundedBlockingDeque[message.InboundMessage](1) - genesisPeer, err := peer.StartTestPeer( - tc.DefaultContext(), - subnetGenesisNode.StakingAddress, - env.GetNetwork().NetworkID, - router.InboundHandlerFunc(func(_ context.Context, m message.InboundMessage) { - genesisPeerMessages.PushRight(m) - }), - ) + tc.By("creating the validator to register") + subnetRegisterNode := e2e.AddEphemeralNode(tc, env.GetNetwork(), tmpnet.FlagsMap{ + config.TrackSubnetsKey: subnetID.String(), + }) + + registerNodePoP, err := subnetRegisterNode.GetProofOfPossession() require.NoError(err) - defer func() { - genesisPeer.StartClose() - require.NoError(genesisPeer.AwaitClosed(tc.DefaultContext())) - }() + registerNodePK, err := bls.PublicKeyFromCompressedBytes(registerNodePoP.PublicKey[:]) + require.NoError(err) + + tc.By("ensures the subnet nodes are healthy") e2e.WaitForHealthy(tc, subnetGenesisNode) + e2e.WaitForHealthy(tc, subnetRegisterNode) + + const registerWeight = 1 + tc.By("create the unsigned warp message to register the validator") + unsignedRegisterSubnetValidator := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( + networkID, + chainID, + must[*payload.AddressedCall](tc)(payload.NewAddressedCall( + address, + must[*warpmessage.RegisterSubnetValidator](tc)(warpmessage.NewRegisterSubnetValidator( + subnetID, + subnetRegisterNode.NodeID, + registerNodePoP.PublicKey, + uint64(time.Now().Add(5*time.Minute).Unix()), + warpmessage.PChainOwner{}, + warpmessage.PChainOwner{}, + registerWeight, // weight + )).Bytes(), + )).Bytes(), + )) + + registerSubnetValidatorRequest, err := wrapWarpSignatureRequest( + chainID, + unsignedRegisterSubnetValidator, + nil, + ) + require.NoError(err) + + tc.By("send the request to sign the warp message") + require.True(genesisPeer.Send(tc.DefaultContext(), registerSubnetValidatorRequest)) + + tc.By("get the signature response") + registerSubnetValidatorSignature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.True(ok) + + tc.By("create the signed warp message to register the validator") + signers := set.NewBits() + signers.Add(0) // [signers] has weight from the genesis peer + + var sigBytes [bls.SignatureLen]byte + copy(sigBytes[:], bls.SignatureToBytes(registerSubnetValidatorSignature)) + registerSubnetValidator, err := warp.NewMessage( + unsignedRegisterSubnetValidator, + &warp.BitSetSignature{ + Signers: signers.Bytes(), + Signature: sigBytes, + }, + ) + require.NoError(err) + + tc.By("register the validator") + _, err = pWallet.IssueRegisterSubnetValidatorTx( + 1, + registerNodePoP.ProofOfPossession, + registerSubnetValidator.Bytes(), + ) + require.NoError(err) + + tc.By("verify that the validator was registered") + height, err = pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) + + subnetValidators, err = pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) + require.NoError(err) + require.Equal( + map[ids.NodeID]*validators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + subnetRegisterNode.NodeID: { + NodeID: subnetRegisterNode.NodeID, + PublicKey: registerNodePK, + Weight: registerWeight, + }, + }, + subnetValidators, + ) }) }) + +func wrapWarpSignatureRequest( + chainID ids.ID, + msg *warp.UnsignedMessage, + justification []byte, +) (p2pmessage.OutboundMessage, error) { + p2pMessageFactory, err := p2pmessage.NewCreator( + logging.NoLog{}, + prometheus.NewRegistry(), + constants.DefaultNetworkCompressionType, + 10*time.Second, + ) + if err != nil { + return nil, err + } + + request := sdk.SignatureRequest{ + Message: msg.Bytes(), + Justification: justification, + } + requestBytes, err := proto.Marshal(&request) + if err != nil { + return nil, err + } + + return p2pMessageFactory.AppRequest( + chainID, + 0, + time.Hour, + p2psdk.PrefixMessage( + p2psdk.ProtocolPrefix(p2psdk.SignatureRequestHandlerID), + requestBytes, + ), + ) +} + +func findMessage[T any]( + q buffer.BlockingDeque[p2pmessage.InboundMessage], + parser func(p2pmessage.InboundMessage) (T, bool), +) (T, bool) { + var messagesToReprocess []p2pmessage.InboundMessage + defer func() { + slices.Reverse(messagesToReprocess) + for _, msg := range messagesToReprocess { + q.PushLeft(msg) + } + }() + + for { + msg, ok := q.PopLeft() + if !ok { + return utils.Zero[T](), false + } + + parsed, ok := parser(msg) + if ok { + return parsed, true + } + + messagesToReprocess = append(messagesToReprocess, msg) + } +} + +func unwrapWarpSignature(msg p2pmessage.InboundMessage) (*bls.Signature, bool) { + appResponse, ok := msg.Message().(*p2ppb.AppResponse) + if !ok { + return nil, false + } + + handlerID, responseBytes, ok := p2psdk.ParseMessage(appResponse.AppBytes) + if !ok || handlerID != p2psdk.SignatureRequestHandlerID { + return nil, false + } + + var response sdk.SignatureResponse + err := proto.Unmarshal(responseBytes, &response) + if err != nil { + return nil, false + } + + warpSignature, err := bls.SignatureFromBytes(response.Signature) + if err != nil { + return nil, false + } + return warpSignature, true +} + +func must[T any](t require.TestingT) func(T, error) T { + return func(val T, err error) T { + require.NoError(t, err) + return val + } +} From 1936cfca521e34443aa50c452ac1cacce2e13acb Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 21:00:57 -0400 Subject: [PATCH 158/199] wip --- tests/e2e/p/permissionless_layer_one.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 03413473f7c4..0074e4876400 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -138,7 +138,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("connecting to the genesis validator") var ( - networkID = env.GetNetwork().NetworkID + networkID = env.GetNetwork().GetNetworkID() genesisPeerMessages = buffer.NewUnboundedBlockingDeque[p2pmessage.InboundMessage](1) ) genesisPeer, err := peer.StartTestPeer( From f92b9c578fc775a24f3e35f678528b0a551a9505 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 21:44:52 -0400 Subject: [PATCH 159/199] wip --- tests/e2e/p/permissionless_layer_one.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 0074e4876400..34ebde60a95c 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -146,6 +146,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { subnetGenesisNode.StakingAddress, networkID, router.InboundHandlerFunc(func(_ context.Context, m p2pmessage.InboundMessage) { + tc.Outf("received %s %s from %s", m.Op(), m.Message(), m.NodeID()) genesisPeerMessages.PushRight(m) }), ) @@ -388,13 +389,8 @@ func unwrapWarpSignature(msg p2pmessage.InboundMessage) (*bls.Signature, bool) { return nil, false } - handlerID, responseBytes, ok := p2psdk.ParseMessage(appResponse.AppBytes) - if !ok || handlerID != p2psdk.SignatureRequestHandlerID { - return nil, false - } - var response sdk.SignatureResponse - err := proto.Unmarshal(responseBytes, &response) + err := proto.Unmarshal(appResponse.AppBytes, &response) if err != nil { return nil, false } From 5875418aaf53ed66ed4b4213c0942c64340c6136 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 21:57:09 -0400 Subject: [PATCH 160/199] nit --- tests/e2e/p/permissionless_layer_one.go | 19 ++++++++++++++++--- vms/platformvm/validators/manager.go | 15 ++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 34ebde60a95c..cf02eb70659f 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -20,7 +20,6 @@ import ( "github.com/ava-labs/avalanchego/network/peer" "github.com/ava-labs/avalanchego/proto/pb/sdk" "github.com/ava-labs/avalanchego/snow/networking/router" - "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/tests/fixture/e2e" "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" "github.com/ava-labs/avalanchego/utils" @@ -41,6 +40,8 @@ import ( p2pmessage "github.com/ava-labs/avalanchego/message" p2psdk "github.com/ava-labs/avalanchego/network/p2p" p2ppb "github.com/ava-labs/avalanchego/proto/pb/p2p" + snowvalidators "github.com/ava-labs/avalanchego/snow/validators" + platformvmvalidators "github.com/ava-labs/avalanchego/vms/platformvm/validators" warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" ) @@ -192,6 +193,18 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) require.NoError(err) + tc.By("waiting to update the proposervm P-chain height") + time.Sleep((5 * platformvmvalidators.RecentlyAcceptedWindowTTL) / 4) + + tc.By("issuing random transactions to update the proposervm P-chain height") + for range 2 { + _, err = pWallet.IssueCreateSubnetTx( + owner, + tc.WithDefaultContext(), + ) + require.NoError(err) + } + tc.By("verifying the Permissioned Subnet was converted to a Permissionless L1") res, err = pClient.GetSubnet(tc.DefaultContext(), subnetID) require.NoError(err) @@ -216,7 +229,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) require.NoError(err) require.Equal( - map[ids.NodeID]*validators.GetValidatorOutput{ + map[ids.NodeID]*snowvalidators.GetValidatorOutput{ subnetGenesisNode.NodeID: { NodeID: subnetGenesisNode.NodeID, PublicKey: genesisNodePK, @@ -304,7 +317,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { subnetValidators, err = pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) require.NoError(err) require.Equal( - map[ids.NodeID]*validators.GetValidatorOutput{ + map[ids.NodeID]*snowvalidators.GetValidatorOutput{ subnetGenesisNode.NodeID: { NodeID: subnetGenesisNode.NodeID, PublicKey: genesisNodePK, diff --git a/vms/platformvm/validators/manager.go b/vms/platformvm/validators/manager.go index 2e67faa63464..7b2be93ccba2 100644 --- a/vms/platformvm/validators/manager.go +++ b/vms/platformvm/validators/manager.go @@ -24,10 +24,11 @@ import ( ) const ( - validatorSetsCacheSize = 64 - maxRecentlyAcceptedWindowSize = 64 - minRecentlyAcceptedWindowSize = 0 - recentlyAcceptedWindowTTL = 30 * time.Second + MaxRecentlyAcceptedWindowSize = 64 + MinRecentlyAcceptedWindowSize = 0 + RecentlyAcceptedWindowTTL = 30 * time.Second + + validatorSetsCacheSize = 64 ) var ( @@ -106,9 +107,9 @@ func NewManager( recentlyAccepted: window.New[ids.ID]( window.Config{ Clock: clk, - MaxSize: maxRecentlyAcceptedWindowSize, - MinSize: minRecentlyAcceptedWindowSize, - TTL: recentlyAcceptedWindowTTL, + MaxSize: MaxRecentlyAcceptedWindowSize, + MinSize: MinRecentlyAcceptedWindowSize, + TTL: RecentlyAcceptedWindowTTL, }, ), } From ad980128681c26e79fa3b780a215944690c7807f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 12:01:32 -0400 Subject: [PATCH 161/199] Test L1 removal --- tests/e2e/p/permissionless_layer_one.go | 491 ++++++++++++++---------- 1 file changed, 298 insertions(+), 193 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index cf02eb70659f..379a6ece289a 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -45,38 +45,47 @@ import ( warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" ) +const ( + genesisWeight = units.Schmeckle + genesisBalance = units.Avax + registerWeight = genesisWeight / 10 + registerBalance = 0 +) + var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc := e2e.NewTestContext() require := require.New(tc) - ginkgo.It("creates a Permissionless L1", func() { + ginkgo.It("creates and updates Permissionless L1", func() { env := e2e.GetEnv(tc) nodeURI := env.GetRandomNodeURI() - infoClient := info.NewClient(nodeURI.URI) - - tc.By("fetching upgrade config") - upgrades, err := infoClient.Upgrades(tc.DefaultContext()) - require.NoError(err) - tc.By("verifying Etna is activated") - now := time.Now() - if !upgrades.IsEtnaActivated(now) { - ginkgo.Skip("Etna is not activated. Permissionless L1s are enabled post-Etna, skipping test.") - } - - keychain := env.NewKeychain() - baseWallet := e2e.NewWallet(tc, keychain, nodeURI) + tc.By("verifying Etna is activated", func() { + infoClient := info.NewClient(nodeURI.URI) + upgrades, err := infoClient.Upgrades(tc.DefaultContext()) + require.NoError(err) - pWallet := baseWallet.P() - pClient := platformvm.NewClient(nodeURI.URI) + now := time.Now() + if !upgrades.IsEtnaActivated(now) { + ginkgo.Skip("Etna is not activated. Permissionless L1s are enabled post-Etna, skipping test.") + } + }) - owner := &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ - keychain.Keys[0].Address(), - }, - } + tc.By("loading the wallet") + var ( + keychain = env.NewKeychain() + baseWallet = e2e.NewWallet(tc, keychain, nodeURI) + pWallet = baseWallet.P() + pClient = platformvm.NewClient(nodeURI.URI) + owner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + } + ) + tc.By("creating the chain genesis") genesisKey, err := secp256k1.NewPrivateKey() require.NoError(err) @@ -91,40 +100,48 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) require.NoError(err) - tc.By("issuing a CreateSubnetTx") - subnetTx, err := pWallet.IssueCreateSubnetTx( - owner, - tc.WithDefaultContext(), - ) - require.NoError(err) + var subnetID ids.ID + tc.By("issuing a CreateSubnetTx", func() { + subnetTx, err := pWallet.IssueCreateSubnetTx( + owner, + tc.WithDefaultContext(), + ) + require.NoError(err) - tc.By("verifying a Permissioned Subnet was successfully created") - subnetID := subnetTx.ID() - require.NotEqual(subnetID, constants.PrimaryNetworkID) + subnetID = subnetTx.ID() + }) - res, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) - require.NoError(err) - require.Equal( - platformvm.GetSubnetClientResponse{ - IsPermissioned: true, - ControlKeys: []ids.ShortID{ - keychain.Keys[0].Address(), + tc.By("verifying a Permissioned Subnet was successfully created", func() { + require.NotEqual(constants.PrimaryNetworkID, subnetID) + + subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) + require.NoError(err) + require.Equal( + platformvm.GetSubnetClientResponse{ + IsPermissioned: true, + ControlKeys: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + Threshold: 1, }, - Threshold: 1, - }, - res, - ) + subnet, + ) + }) - tc.By("issuing a CreateChainTx") - chainTx, err := pWallet.IssueCreateChainTx( - subnetID, - genesisBytes, - constants.XSVMID, - nil, - "No Permissions", - tc.WithDefaultContext(), - ) - require.NoError(err) + var chainID ids.ID + tc.By("issuing a CreateChainTx", func() { + chainTx, err := pWallet.IssueCreateChainTx( + subnetID, + genesisBytes, + constants.XSVMID, + nil, + "No Permissions", + tc.WithDefaultContext(), + ) + require.NoError(err) + + chainID = chainTx.ID() + }) tc.By("creating the genesis validator") subnetGenesisNode := e2e.AddEphemeralNode(tc, env.GetNetwork(), tmpnet.FlagsMap{ @@ -153,91 +170,96 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ) require.NoError(err) defer func() { + genesisPeerMessages.Close() genesisPeer.StartClose() require.NoError(genesisPeer.AwaitClosed(tc.DefaultContext())) }() - const genesisWeight = 100 - var ( - chainID = chainTx.ID() - address = []byte{} - ) - tc.By("issuing a ConvertSubnetTx") - _, err = pWallet.IssueConvertSubnetTx( - subnetID, - chainID, - address, - []*txs.ConvertSubnetValidator{ - { - NodeID: subnetGenesisNode.NodeID.Bytes(), - Weight: genesisWeight, - Balance: units.Avax, - Signer: *genesisNodePoP, - }, - }, - tc.WithDefaultContext(), - ) - require.NoError(err) - - expectedConversionID, err := warpmessage.SubnetConversionID(warpmessage.SubnetConversionData{ - SubnetID: subnetID, - ManagerChainID: chainID, - ManagerAddress: address, - Validators: []warpmessage.SubnetConversionValidatorData{ - { - NodeID: subnetGenesisNode.NodeID.Bytes(), - BLSPublicKey: genesisNodePoP.PublicKey, - Weight: genesisWeight, + address := []byte{} + tc.By("issuing a ConvertSubnetTx", func() { + _, err := pWallet.IssueConvertSubnetTx( + subnetID, + chainID, + address, + []*txs.ConvertSubnetValidator{ + { + NodeID: subnetGenesisNode.NodeID.Bytes(), + Weight: genesisWeight, + Balance: genesisBalance, + Signer: *genesisNodePoP, + }, }, - }, - }) - require.NoError(err) - - tc.By("waiting to update the proposervm P-chain height") - time.Sleep((5 * platformvmvalidators.RecentlyAcceptedWindowTTL) / 4) - - tc.By("issuing random transactions to update the proposervm P-chain height") - for range 2 { - _, err = pWallet.IssueCreateSubnetTx( - owner, tc.WithDefaultContext(), ) require.NoError(err) - } + }) - tc.By("verifying the Permissioned Subnet was converted to a Permissionless L1") - res, err = pClient.GetSubnet(tc.DefaultContext(), subnetID) - require.NoError(err) - require.Equal( - platformvm.GetSubnetClientResponse{ - IsPermissioned: false, - ControlKeys: []ids.ShortID{ - keychain.Keys[0].Address(), - }, - Threshold: 1, - ConversionID: expectedConversionID, + tc.By("verifying the Permissioned Subnet was converted to a Permissionless L1", func() { + expectedConversionID, err := warpmessage.SubnetConversionID(warpmessage.SubnetConversionData{ + SubnetID: subnetID, ManagerChainID: chainID, ManagerAddress: address, - }, - res, - ) + Validators: []warpmessage.SubnetConversionValidatorData{ + { + NodeID: subnetGenesisNode.NodeID.Bytes(), + BLSPublicKey: genesisNodePoP.PublicKey, + Weight: genesisWeight, + }, + }, + }) + require.NoError(err) - tc.By("verifying the Permissionless L1 reports the correct validator set") - height, err := pClient.GetHeight(tc.DefaultContext()) - require.NoError(err) + subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) + require.NoError(err) + require.Equal( + platformvm.GetSubnetClientResponse{ + IsPermissioned: false, + ControlKeys: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + Threshold: 1, + ConversionID: expectedConversionID, + ManagerChainID: chainID, + ManagerAddress: address, + }, + subnet, + ) + }) - subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) - require.NoError(err) - require.Equal( - map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, + tc.By("verifying the Permissionless L1 reports the correct validator set", func() { + height, err := pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) + + subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) + require.NoError(err) + require.Equal( + map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, }, - }, - subnetValidators, - ) + subnetValidators, + ) + }) + + advanceProposerVMPChainHeight := func() { + // We first must wait at least [RecentlyAcceptedWindowTTL] to ensure + // the next block will evict the prior block from the windower. + time.Sleep((5 * platformvmvalidators.RecentlyAcceptedWindowTTL) / 4) + + // Now we must: + // 1. issue a block which should include the old P-chain height. + // 2. issue a block which should include the new P-chain height. + for range 2 { + _, err = pWallet.IssueBaseTx(nil, tc.WithDefaultContext()) + require.NoError(err) + } + // Now that a block has been issued with the new P-chain height, the + // next block will use that height for warp message verification. + } + tc.By("advancing the proposervm P-chain height", advanceProposerVMPChainHeight) tc.By("creating the validator to register") subnetRegisterNode := e2e.AddEphemeralNode(tc, env.GetNetwork(), tmpnet.FlagsMap{ @@ -250,87 +272,170 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { registerNodePK, err := bls.PublicKeyFromCompressedBytes(registerNodePoP.PublicKey[:]) require.NoError(err) - tc.By("ensures the subnet nodes are healthy") - e2e.WaitForHealthy(tc, subnetGenesisNode) - e2e.WaitForHealthy(tc, subnetRegisterNode) - - const registerWeight = 1 - tc.By("create the unsigned warp message to register the validator") - unsignedRegisterSubnetValidator := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( - networkID, - chainID, - must[*payload.AddressedCall](tc)(payload.NewAddressedCall( - address, - must[*warpmessage.RegisterSubnetValidator](tc)(warpmessage.NewRegisterSubnetValidator( - subnetID, - subnetRegisterNode.NodeID, - registerNodePoP.PublicKey, - uint64(time.Now().Add(5*time.Minute).Unix()), - warpmessage.PChainOwner{}, - warpmessage.PChainOwner{}, - registerWeight, // weight - )).Bytes(), - )).Bytes(), - )) + tc.By("ensuring the subnet nodes are healthy", func() { + e2e.WaitForHealthy(tc, subnetGenesisNode) + e2e.WaitForHealthy(tc, subnetRegisterNode) + }) - registerSubnetValidatorRequest, err := wrapWarpSignatureRequest( - chainID, - unsignedRegisterSubnetValidator, - nil, + tc.By("creating the RegisterSubnetValidatorMessage") + registerSubnetValidatorMessage, err := warpmessage.NewRegisterSubnetValidator( + subnetID, + subnetRegisterNode.NodeID, + registerNodePoP.PublicKey, + uint64(time.Now().Add(5*time.Minute).Unix()), + warpmessage.PChainOwner{}, + warpmessage.PChainOwner{}, + registerWeight, ) require.NoError(err) + registerValidationID := registerSubnetValidatorMessage.ValidationID() + + tc.By("registering the validator", func() { + tc.By("creating the unsigned warp message") + unsignedRegisterSubnetValidator := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( + networkID, + chainID, + must[*payload.AddressedCall](tc)(payload.NewAddressedCall( + address, + registerSubnetValidatorMessage.Bytes(), + )).Bytes(), + )) + + tc.By("sending the request to sign the warp message", func() { + registerSubnetValidatorRequest, err := wrapWarpSignatureRequest( + chainID, + unsignedRegisterSubnetValidator, + nil, + ) + require.NoError(err) + + require.True(genesisPeer.Send(tc.DefaultContext(), registerSubnetValidatorRequest)) + }) + + tc.By("getting the signature response") + registerSubnetValidatorSignature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.True(ok) + + tc.By("creating the signed warp message to register the validator") + signers := set.NewBits() + signers.Add(0) // [signers] has weight from the genesis peer + + var sigBytes [bls.SignatureLen]byte + copy(sigBytes[:], bls.SignatureToBytes(registerSubnetValidatorSignature)) + registerSubnetValidator, err := warp.NewMessage( + unsignedRegisterSubnetValidator, + &warp.BitSetSignature{ + Signers: signers.Bytes(), + Signature: sigBytes, + }, + ) + require.NoError(err) - tc.By("send the request to sign the warp message") - require.True(genesisPeer.Send(tc.DefaultContext(), registerSubnetValidatorRequest)) + tc.By("issuing a RegisterSubnetValidatorTx", func() { + _, err := pWallet.IssueRegisterSubnetValidatorTx( + registerBalance, + registerNodePoP.ProofOfPossession, + registerSubnetValidator.Bytes(), + ) + require.NoError(err) + }) + }) - tc.By("get the signature response") - registerSubnetValidatorSignature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) - require.True(ok) + tc.By("verifying the Permissionless L1 reports the correct validator set", func() { + height, err := pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) - tc.By("create the signed warp message to register the validator") - signers := set.NewBits() - signers.Add(0) // [signers] has weight from the genesis peer + subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) + require.NoError(err) + require.Equal( + map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + subnetRegisterNode.NodeID: { + NodeID: subnetRegisterNode.NodeID, + PublicKey: registerNodePK, + Weight: registerWeight, + }, + }, + subnetValidators, + ) + }) - var sigBytes [bls.SignatureLen]byte - copy(sigBytes[:], bls.SignatureToBytes(registerSubnetValidatorSignature)) - registerSubnetValidator, err := warp.NewMessage( - unsignedRegisterSubnetValidator, - &warp.BitSetSignature{ - Signers: signers.Bytes(), - Signature: sigBytes, - }, - ) - require.NoError(err) + tc.By("advancing the proposervm P-chain height", advanceProposerVMPChainHeight) + + tc.By("removing the registered validator", func() { + tc.By("creating the unsigned warp message") + unsignedSubnetValidatorWeight := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( + networkID, + chainID, + must[*payload.AddressedCall](tc)(payload.NewAddressedCall( + address, + must[*warpmessage.SubnetValidatorWeight](tc)(warpmessage.NewSubnetValidatorWeight( + registerValidationID, + 0, // nonce can be anything here + 0, // weight of 0 means the validator is removed + )).Bytes(), + )).Bytes(), + )) + + tc.By("sending the request to sign the warp message", func() { + setSubnetValidatorWeightRequest, err := wrapWarpSignatureRequest( + chainID, + unsignedSubnetValidatorWeight, + nil, + ) + require.NoError(err) + + require.True(genesisPeer.Send(tc.DefaultContext(), setSubnetValidatorWeightRequest)) + }) + + tc.By("getting the signature response") + setSubnetValidatorWeightSignature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.True(ok) + + tc.By("creating the signed warp message to remove the validator") + signers := set.NewBits() + signers.Add(0) // [signers] has weight from the genesis peer + + var sigBytes [bls.SignatureLen]byte + copy(sigBytes[:], bls.SignatureToBytes(setSubnetValidatorWeightSignature)) + registerSubnetValidator, err := warp.NewMessage( + unsignedSubnetValidatorWeight, + &warp.BitSetSignature{ + Signers: signers.Bytes(), + Signature: sigBytes, + }, + ) + require.NoError(err) - tc.By("register the validator") - _, err = pWallet.IssueRegisterSubnetValidatorTx( - 1, - registerNodePoP.ProofOfPossession, - registerSubnetValidator.Bytes(), - ) - require.NoError(err) + tc.By("issuing a SetSubnetValidatorWeightTx", func() { + _, err := pWallet.IssueSetSubnetValidatorWeightTx( + registerSubnetValidator.Bytes(), + ) + require.NoError(err) + }) + }) - tc.By("verify that the validator was registered") - height, err = pClient.GetHeight(tc.DefaultContext()) - require.NoError(err) + tc.By("verifying the Permissionless L1 reports the correct validator set", func() { + height, err := pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) - subnetValidators, err = pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) - require.NoError(err) - require.Equal( - map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, - subnetRegisterNode.NodeID: { - NodeID: subnetRegisterNode.NodeID, - PublicKey: registerNodePK, - Weight: registerWeight, + subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) + require.NoError(err) + require.Equal( + map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, }, - }, - subnetValidators, - ) + subnetValidators, + ) + }) }) }) From 568a9ae4b8c84da7773edbd14e2e5e2d69093280 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 12:29:06 -0400 Subject: [PATCH 162/199] Verify P-chain signing support --- tests/e2e/p/permissionless_layer_one.go | 222 +++++++++++++++++------- 1 file changed, 160 insertions(+), 62 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 379a6ece289a..2218a8399215 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -209,39 +209,71 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) require.NoError(err) - subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) - require.NoError(err) - require.Equal( - platformvm.GetSubnetClientResponse{ - IsPermissioned: false, - ControlKeys: []ids.ShortID{ - keychain.Keys[0].Address(), + tc.By("verifying the subnet reports as being converted", func() { + subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) + require.NoError(err) + require.Equal( + platformvm.GetSubnetClientResponse{ + IsPermissioned: false, + ControlKeys: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + Threshold: 1, + ConversionID: expectedConversionID, + ManagerChainID: chainID, + ManagerAddress: address, }, - Threshold: 1, - ConversionID: expectedConversionID, - ManagerChainID: chainID, - ManagerAddress: address, - }, - subnet, - ) - }) + subnet, + ) + }) - tc.By("verifying the Permissionless L1 reports the correct validator set", func() { - height, err := pClient.GetHeight(tc.DefaultContext()) - require.NoError(err) + tc.By("verifying the validator set was updated", func() { + height, err := pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) - subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) - require.NoError(err) - require.Equal( - map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, + subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) + require.NoError(err) + require.Equal( + map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, }, - }, - subnetValidators, - ) + subnetValidators, + ) + }) + + tc.By("fetching the subnet conversion attestation", func() { + unsignedSubnetConversion := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( + networkID, + chainID, + must[*payload.AddressedCall](tc)(payload.NewAddressedCall( + address, + must[*warpmessage.SubnetConversion](tc)(warpmessage.NewSubnetConversion( + expectedConversionID, + )).Bytes(), + )).Bytes(), + )) + + tc.By("sending the request to sign the warp message", func() { + registerSubnetValidatorRequest, err := wrapWarpSignatureRequest( + constants.PlatformChainID, + unsignedSubnetConversion, + subnetID[:], + ) + require.NoError(err) + + require.True(genesisPeer.Send(tc.DefaultContext(), registerSubnetValidatorRequest)) + }) + + tc.By("getting the signature response", func() { + signature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.True(ok) + require.True(bls.Verify(genesisNodePK, signature, unsignedSubnetConversion.Bytes())) + }) + }) }) advanceProposerVMPChainHeight := func() { @@ -341,27 +373,60 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) }) - tc.By("verifying the Permissionless L1 reports the correct validator set", func() { - height, err := pClient.GetHeight(tc.DefaultContext()) - require.NoError(err) + tc.By("verifying the validator was registered", func() { + tc.By("verifying the validator set was updated", func() { + height, err := pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) - subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) - require.NoError(err) - require.Equal( - map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, - subnetRegisterNode.NodeID: { - NodeID: subnetRegisterNode.NodeID, - PublicKey: registerNodePK, - Weight: registerWeight, + subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) + require.NoError(err) + require.Equal( + map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + subnetRegisterNode.NodeID: { + NodeID: subnetRegisterNode.NodeID, + PublicKey: registerNodePK, + Weight: registerWeight, + }, }, - }, - subnetValidators, - ) + subnetValidators, + ) + }) + + tc.By("fetching the validator registration attestation", func() { + unsignedSubnetValidatorRegistration := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( + networkID, + chainID, + must[*payload.AddressedCall](tc)(payload.NewAddressedCall( + address, + must[*warpmessage.SubnetValidatorRegistration](tc)(warpmessage.NewSubnetValidatorRegistration( + registerValidationID, + true, // registered + )).Bytes(), + )).Bytes(), + )) + + tc.By("sending the request to sign the warp message", func() { + subnetValidatorRegistrationRequest, err := wrapWarpSignatureRequest( + constants.PlatformChainID, + unsignedSubnetValidatorRegistration, + nil, + ) + require.NoError(err) + + require.True(genesisPeer.Send(tc.DefaultContext(), subnetValidatorRegistrationRequest)) + }) + + tc.By("getting the signature response", func() { + signature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.True(ok) + require.True(bls.Verify(genesisNodePK, signature, unsignedSubnetValidatorRegistration.Bytes())) + }) + }) }) tc.By("advancing the proposervm P-chain height", advanceProposerVMPChainHeight) @@ -419,22 +484,55 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) }) - tc.By("verifying the Permissionless L1 reports the correct validator set", func() { - height, err := pClient.GetHeight(tc.DefaultContext()) - require.NoError(err) + tc.By("verifying the validator was removed", func() { + tc.By("verifying the validator set was updated", func() { + height, err := pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) - subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) - require.NoError(err) - require.Equal( - map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, + subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) + require.NoError(err) + require.Equal( + map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, }, - }, - subnetValidators, - ) + subnetValidators, + ) + }) + + tc.By("fetching the validator removal attestation", func() { + unsignedSubnetValidatorRegistration := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( + networkID, + chainID, + must[*payload.AddressedCall](tc)(payload.NewAddressedCall( + address, + must[*warpmessage.SubnetValidatorRegistration](tc)(warpmessage.NewSubnetValidatorRegistration( + registerValidationID, + true, // removed + )).Bytes(), + )).Bytes(), + )) + + tc.By("sending the request to sign the warp message", func() { + subnetValidatorRegistrationRequest, err := wrapWarpSignatureRequest( + constants.PlatformChainID, + unsignedSubnetValidatorRegistration, + registerSubnetValidatorMessage.Bytes(), + ) + require.NoError(err) + + require.True(genesisPeer.Send(tc.DefaultContext(), subnetValidatorRegistrationRequest)) + }) + + tc.By("getting the signature response", func() { + signature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.True(ok) + require.True(bls.Verify(genesisNodePK, signature, unsignedSubnetValidatorRegistration.Bytes())) + }) + }) }) }) }) From 8453ee10f9c9b4d1b07ef71400314b2799ea147c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 12:31:38 -0400 Subject: [PATCH 163/199] fix test --- tests/e2e/p/permissionless_layer_one.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 379a6ece289a..633cb6451430 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -354,10 +354,9 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { PublicKey: genesisNodePK, Weight: genesisWeight, }, - subnetRegisterNode.NodeID: { - NodeID: subnetRegisterNode.NodeID, - PublicKey: registerNodePK, - Weight: registerWeight, + ids.EmptyNodeID: { // The validator is not active + NodeID: ids.EmptyNodeID, + Weight: registerWeight, }, }, subnetValidators, From 665c49961a13005888df35dcdda1fd219b0e2f30 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 12:37:02 -0400 Subject: [PATCH 164/199] nit --- tests/e2e/p/permissionless_layer_one.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 633cb6451430..459b2fcd9630 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -269,9 +269,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { registerNodePoP, err := subnetRegisterNode.GetProofOfPossession() require.NoError(err) - registerNodePK, err := bls.PublicKeyFromCompressedBytes(registerNodePoP.PublicKey[:]) - require.NoError(err) - tc.By("ensuring the subnet nodes are healthy", func() { e2e.WaitForHealthy(tc, subnetGenesisNode) e2e.WaitForHealthy(tc, subnetRegisterNode) From 3a427d7024923ba9459b2ae79f79fb289aa77afb Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 12:42:55 -0400 Subject: [PATCH 165/199] Briefly activate the validator --- tests/e2e/p/permissionless_layer_one.go | 34 +++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index e201c27bb864..b0ad51d874e3 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -301,6 +301,9 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { registerNodePoP, err := subnetRegisterNode.GetProofOfPossession() require.NoError(err) + registerNodePK, err := bls.PublicKeyFromCompressedBytes(registerNodePoP.PublicKey[:]) + require.NoError(err) + tc.By("ensuring the subnet nodes are healthy", func() { e2e.WaitForHealthy(tc, subnetGenesisNode) e2e.WaitForHealthy(tc, subnetRegisterNode) @@ -425,6 +428,37 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) }) + tc.By("issuing an IncreaseBalanceTx", func() { + _, err := pWallet.IssueIncreaseBalanceTx( + registerValidationID, + units.NanoAvax, + ) + require.NoError(err) + }) + + tc.By("verifying the validator was activated", func() { + height, err := pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) + + subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) + require.NoError(err) + require.Equal( + map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + subnetRegisterNode.NodeID: { + NodeID: subnetRegisterNode.NodeID, + PublicKey: registerNodePK, + Weight: registerWeight, + }, + }, + subnetValidators, + ) + }) + tc.By("advancing the proposervm P-chain height", advanceProposerVMPChainHeight) tc.By("removing the registered validator", func() { From 875fb4a1c6c05e168d30bee44e17c726bce2a9d7 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 12:44:25 -0400 Subject: [PATCH 166/199] Deactivate the validator --- tests/e2e/p/permissionless_layer_one.go | 31 ++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index b0ad51d874e3..252217088f9d 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -431,7 +431,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("issuing an IncreaseBalanceTx", func() { _, err := pWallet.IssueIncreaseBalanceTx( registerValidationID, - units.NanoAvax, + units.Avax, ) require.NoError(err) }) @@ -459,6 +459,35 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ) }) + tc.By("issuing an DisableSubnetValidatorTx", func() { + _, err := pWallet.IssueDisableSubnetValidatorTx( + registerValidationID, + ) + require.NoError(err) + }) + + tc.By("verifying the validator was deactivated", func() { + height, err := pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) + + subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) + require.NoError(err) + require.Equal( + map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + ids.EmptyNodeID: { + NodeID: ids.EmptyNodeID, + Weight: registerWeight, + }, + }, + subnetValidators, + ) + }) + tc.By("advancing the proposervm P-chain height", advanceProposerVMPChainHeight) tc.By("removing the registered validator", func() { From 8b32e95f942e183aeff72d635fa26399a01568b8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 13:03:07 -0400 Subject: [PATCH 167/199] nit --- tests/e2e/p/permissionless_layer_one.go | 33 ++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index e201c27bb864..71fd2241b44a 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -177,7 +177,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { address := []byte{} tc.By("issuing a ConvertSubnetTx", func() { - _, err := pWallet.IssueConvertSubnetTx( + tx, err := pWallet.IssueConvertSubnetTx( subnetID, chainID, address, @@ -192,6 +192,15 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.WithDefaultContext(), ) require.NoError(err) + + tc.By("ensuring the genesis peer has accepted the tx", func() { + require.NoError(platformvm.AwaitTxAccepted( + platformvm.NewClient(subnetGenesisNode.URI), + tc.DefaultContext(), + tx.ID(), + time.Second, + )) + }) }) tc.By("verifying the Permissioned Subnet was converted to a Permissionless L1", func() { @@ -361,12 +370,21 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { require.NoError(err) tc.By("issuing a RegisterSubnetValidatorTx", func() { - _, err := pWallet.IssueRegisterSubnetValidatorTx( + tx, err := pWallet.IssueRegisterSubnetValidatorTx( registerBalance, registerNodePoP.ProofOfPossession, registerSubnetValidator.Bytes(), ) require.NoError(err) + + tc.By("ensuring the genesis peer has accepted the tx", func() { + require.NoError(platformvm.AwaitTxAccepted( + platformvm.NewClient(subnetGenesisNode.URI), + tc.DefaultContext(), + tx.ID(), + time.Second, + )) + }) }) }) @@ -473,10 +491,19 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { require.NoError(err) tc.By("issuing a SetSubnetValidatorWeightTx", func() { - _, err := pWallet.IssueSetSubnetValidatorWeightTx( + tx, err := pWallet.IssueSetSubnetValidatorWeightTx( registerSubnetValidator.Bytes(), ) require.NoError(err) + + tc.By("ensuring the genesis peer has accepted the tx", func() { + require.NoError(platformvm.AwaitTxAccepted( + platformvm.NewClient(subnetGenesisNode.URI), + tc.DefaultContext(), + tx.ID(), + time.Second, + )) + }) }) }) From a0f433517d15871c6bf59334e6c4a71d77c2220b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 13:07:07 -0400 Subject: [PATCH 168/199] nit --- tests/e2e/p/permissionless_layer_one.go | 31 ++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 71fd2241b44a..9ceb8e9d1c67 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -31,7 +31,6 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/example/xsvm/genesis" - "github.com/ava-labs/avalanchego/vms/platformvm" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -40,7 +39,9 @@ import ( p2pmessage "github.com/ava-labs/avalanchego/message" p2psdk "github.com/ava-labs/avalanchego/network/p2p" p2ppb "github.com/ava-labs/avalanchego/proto/pb/p2p" + platformvmpb "github.com/ava-labs/avalanchego/proto/pb/platformvm" snowvalidators "github.com/ava-labs/avalanchego/snow/validators" + platformvmsdk "github.com/ava-labs/avalanchego/vms/platformvm" platformvmvalidators "github.com/ava-labs/avalanchego/vms/platformvm/validators" warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" ) @@ -76,7 +77,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { keychain = env.NewKeychain() baseWallet = e2e.NewWallet(tc, keychain, nodeURI) pWallet = baseWallet.P() - pClient = platformvm.NewClient(nodeURI.URI) + pClient = platformvmsdk.NewClient(nodeURI.URI) owner = &secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{ @@ -117,7 +118,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) require.NoError(err) require.Equal( - platformvm.GetSubnetClientResponse{ + platformvmsdk.GetSubnetClientResponse{ IsPermissioned: true, ControlKeys: []ids.ShortID{ keychain.Keys[0].Address(), @@ -194,8 +195,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { require.NoError(err) tc.By("ensuring the genesis peer has accepted the tx", func() { - require.NoError(platformvm.AwaitTxAccepted( - platformvm.NewClient(subnetGenesisNode.URI), + require.NoError(platformvmsdk.AwaitTxAccepted( + platformvmsdk.NewClient(subnetGenesisNode.URI), tc.DefaultContext(), tx.ID(), time.Second, @@ -222,7 +223,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) require.NoError(err) require.Equal( - platformvm.GetSubnetClientResponse{ + platformvmsdk.GetSubnetClientResponse{ IsPermissioned: false, ControlKeys: []ids.ShortID{ keychain.Keys[0].Address(), @@ -378,8 +379,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { require.NoError(err) tc.By("ensuring the genesis peer has accepted the tx", func() { - require.NoError(platformvm.AwaitTxAccepted( - platformvm.NewClient(subnetGenesisNode.URI), + require.NoError(platformvmsdk.AwaitTxAccepted( + platformvmsdk.NewClient(subnetGenesisNode.URI), tc.DefaultContext(), tx.ID(), time.Second, @@ -497,8 +498,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { require.NoError(err) tc.By("ensuring the genesis peer has accepted the tx", func() { - require.NoError(platformvm.AwaitTxAccepted( - platformvm.NewClient(subnetGenesisNode.URI), + require.NoError(platformvmsdk.AwaitTxAccepted( + platformvmsdk.NewClient(subnetGenesisNode.URI), tc.DefaultContext(), tx.ID(), time.Second, @@ -539,11 +540,19 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { )).Bytes(), )) + justification := platformvmpb.SubnetValidatorRegistrationJustification{ + Preimage: &platformvmpb.SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage{ + RegisterSubnetValidatorMessage: registerSubnetValidatorMessage.Bytes(), + }, + } + justificationBytes, err := proto.Marshal(&justification) + require.NoError(err) + tc.By("sending the request to sign the warp message", func() { subnetValidatorRegistrationRequest, err := wrapWarpSignatureRequest( constants.PlatformChainID, unsignedSubnetValidatorRegistration, - registerSubnetValidatorMessage.Bytes(), + justificationBytes, ) require.NoError(err) From 591c770efa2c5afa89b42354ca8db87f7a3e39be Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 13:14:28 -0400 Subject: [PATCH 169/199] nit --- tests/e2e/p/permissionless_layer_one.go | 54 +++++++++++++++---------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 9ceb8e9d1c67..820b6fe11136 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -5,6 +5,7 @@ package p import ( "context" + "errors" "math" "slices" "time" @@ -279,7 +280,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) tc.By("getting the signature response", func() { - signature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + signature, ok, err := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.NoError(err) require.True(ok) require.True(bls.Verify(genesisNodePK, signature, unsignedSubnetConversion.Bytes())) }) @@ -352,7 +354,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) tc.By("getting the signature response") - registerSubnetValidatorSignature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + registerSubnetValidatorSignature, ok, err := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.NoError(err) require.True(ok) tc.By("creating the signed warp message to register the validator") @@ -437,7 +440,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) tc.By("getting the signature response", func() { - signature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + signature, ok, err := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.NoError(err) require.True(ok) require.True(bls.Verify(genesisNodePK, signature, unsignedSubnetValidatorRegistration.Bytes())) }) @@ -473,7 +477,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) tc.By("getting the signature response") - setSubnetValidatorWeightSignature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + setSubnetValidatorWeightSignature, ok, err := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.NoError(err) require.True(ok) tc.By("creating the signed warp message to remove the validator") @@ -560,7 +565,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) tc.By("getting the signature response", func() { - signature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + signature, ok, err := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.NoError(err) require.True(ok) require.True(bls.Verify(genesisNodePK, signature, unsignedSubnetValidatorRegistration.Bytes())) }) @@ -606,8 +612,8 @@ func wrapWarpSignatureRequest( func findMessage[T any]( q buffer.BlockingDeque[p2pmessage.InboundMessage], - parser func(p2pmessage.InboundMessage) (T, bool), -) (T, bool) { + parser func(p2pmessage.InboundMessage) (T, bool, error), +) (T, bool, error) { var messagesToReprocess []p2pmessage.InboundMessage defer func() { slices.Reverse(messagesToReprocess) @@ -619,35 +625,41 @@ func findMessage[T any]( for { msg, ok := q.PopLeft() if !ok { - return utils.Zero[T](), false + return utils.Zero[T](), false, nil } - parsed, ok := parser(msg) + parsed, ok, err := parser(msg) + if err != nil { + return utils.Zero[T](), false, err + } if ok { - return parsed, true + return parsed, true, nil } messagesToReprocess = append(messagesToReprocess, msg) } } -func unwrapWarpSignature(msg p2pmessage.InboundMessage) (*bls.Signature, bool) { - appResponse, ok := msg.Message().(*p2ppb.AppResponse) - if !ok { - return nil, false +// unwrapWarpSignature assumes the only type of AppResponses that will be +// received are ACP-118 compliant responses. +func unwrapWarpSignature(msg p2pmessage.InboundMessage) (*bls.Signature, bool, error) { + var appResponse *p2ppb.AppResponse + switch msg := msg.Message().(type) { + case *p2ppb.AppResponse: + appResponse = msg + case *p2ppb.AppError: + return nil, false, errors.New(msg.ErrorMessage) + default: + return nil, false, nil } var response sdk.SignatureResponse - err := proto.Unmarshal(appResponse.AppBytes, &response) - if err != nil { - return nil, false + if err := proto.Unmarshal(appResponse.AppBytes, &response); err != nil { + return nil, false, err } warpSignature, err := bls.SignatureFromBytes(response.Signature) - if err != nil { - return nil, false - } - return warpSignature, true + return warpSignature, true, err } func must[T any](t require.TestingT) func(T, error) T { From be70aa4e0ee1ed842f62a6f294f38b8e632b6cf2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 13:29:53 -0400 Subject: [PATCH 170/199] nit --- tests/e2e/p/permissionless_layer_one.go | 44 +++++++++++++++---------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 820b6fe11136..07e75faf58dd 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -21,6 +21,7 @@ import ( "github.com/ava-labs/avalanchego/network/peer" "github.com/ava-labs/avalanchego/proto/pb/sdk" "github.com/ava-labs/avalanchego/snow/networking/router" + "github.com/ava-labs/avalanchego/tests" "github.com/ava-labs/avalanchego/tests/fixture/e2e" "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" "github.com/ava-labs/avalanchego/utils" @@ -32,6 +33,7 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/example/xsvm/genesis" + "github.com/ava-labs/avalanchego/vms/platformvm/status" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -196,12 +198,14 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { require.NoError(err) tc.By("ensuring the genesis peer has accepted the tx", func() { - require.NoError(platformvmsdk.AwaitTxAccepted( - platformvmsdk.NewClient(subnetGenesisNode.URI), - tc.DefaultContext(), - tx.ID(), - time.Second, - )) + var ( + client = platformvmsdk.NewClient(subnetGenesisNode.URI) + txID = tx.ID() + ) + require.Eventually(func() bool { + res, err := client.GetTxStatus(tc.DefaultContext(), txID) + return err != nil && res.Status == status.Committed + }, tests.DefaultTimeout, 100*time.Millisecond) }) }) @@ -382,12 +386,14 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { require.NoError(err) tc.By("ensuring the genesis peer has accepted the tx", func() { - require.NoError(platformvmsdk.AwaitTxAccepted( - platformvmsdk.NewClient(subnetGenesisNode.URI), - tc.DefaultContext(), - tx.ID(), - time.Second, - )) + var ( + client = platformvmsdk.NewClient(subnetGenesisNode.URI) + txID = tx.ID() + ) + require.Eventually(func() bool { + res, err := client.GetTxStatus(tc.DefaultContext(), txID) + return err != nil && res.Status == status.Committed + }, tests.DefaultTimeout, 100*time.Millisecond) }) }) }) @@ -503,12 +509,14 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { require.NoError(err) tc.By("ensuring the genesis peer has accepted the tx", func() { - require.NoError(platformvmsdk.AwaitTxAccepted( - platformvmsdk.NewClient(subnetGenesisNode.URI), - tc.DefaultContext(), - tx.ID(), - time.Second, - )) + var ( + client = platformvmsdk.NewClient(subnetGenesisNode.URI) + txID = tx.ID() + ) + require.Eventually(func() bool { + res, err := client.GetTxStatus(tc.DefaultContext(), txID) + return err != nil && res.Status == status.Committed + }, tests.DefaultTimeout, 100*time.Millisecond) }) }) }) From 426019747265ff8ed1b652b2987a5d84edfc8852 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 13:37:00 -0400 Subject: [PATCH 171/199] nit --- tests/e2e/p/permissionless_layer_one.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 07e75faf58dd..93f4dc6a1c8d 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -508,7 +508,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ) require.NoError(err) - tc.By("ensuring the genesis peer has accepted the tx", func() { + tc.By("ensuring the genesis peer has accepted the tx at "+subnetGenesisNode.URI, func() { var ( client = platformvmsdk.NewClient(subnetGenesisNode.URI) txID = tx.ID() From aaf2553f04761f880f6bf08e2aa1597e3f1eb6b1 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 13:42:24 -0400 Subject: [PATCH 172/199] nit --- tests/e2e/p/permissionless_layer_one.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 93f4dc6a1c8d..d5ef905a64ed 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -197,7 +197,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ) require.NoError(err) - tc.By("ensuring the genesis peer has accepted the tx", func() { + tc.By("ensuring the genesis peer has accepted the tx at "+subnetGenesisNode.URI, func() { var ( client = platformvmsdk.NewClient(subnetGenesisNode.URI) txID = tx.ID() @@ -385,7 +385,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ) require.NoError(err) - tc.By("ensuring the genesis peer has accepted the tx", func() { + tc.By("ensuring the genesis peer has accepted the tx at "+subnetGenesisNode.URI, func() { var ( client = platformvmsdk.NewClient(subnetGenesisNode.URI) txID = tx.ID() From 94d865cce9510b00c6e1f4f3c19536c313bc0635 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 13:56:54 -0400 Subject: [PATCH 173/199] wip --- tests/e2e/p/permissionless_layer_one.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index d5ef905a64ed..226a1b6c6a5c 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -33,7 +33,6 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/example/xsvm/genesis" - "github.com/ava-labs/avalanchego/vms/platformvm/status" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -203,8 +202,9 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { txID = tx.ID() ) require.Eventually(func() bool { - res, err := client.GetTxStatus(tc.DefaultContext(), txID) - return err != nil && res.Status == status.Committed + _, err := client.GetTx(tc.DefaultContext(), txID) + tc.Outf("err: %v", err) + return err == nil }, tests.DefaultTimeout, 100*time.Millisecond) }) }) @@ -391,8 +391,9 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { txID = tx.ID() ) require.Eventually(func() bool { - res, err := client.GetTxStatus(tc.DefaultContext(), txID) - return err != nil && res.Status == status.Committed + _, err := client.GetTx(tc.DefaultContext(), txID) + tc.Outf("err: %v", err) + return err == nil }, tests.DefaultTimeout, 100*time.Millisecond) }) }) @@ -514,8 +515,9 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { txID = tx.ID() ) require.Eventually(func() bool { - res, err := client.GetTxStatus(tc.DefaultContext(), txID) - return err != nil && res.Status == status.Committed + _, err := client.GetTx(tc.DefaultContext(), txID) + tc.Outf("err: %v", err) + return err == nil }, tests.DefaultTimeout, 100*time.Millisecond) }) }) From 26ff5fdaa674fc51be7d697a3450d10b22206602 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 14:11:55 -0400 Subject: [PATCH 174/199] wip --- tests/e2e/p/permissionless_layer_one.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 226a1b6c6a5c..35f1381cbbde 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -167,7 +167,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { subnetGenesisNode.StakingAddress, networkID, router.InboundHandlerFunc(func(_ context.Context, m p2pmessage.InboundMessage) { - tc.Outf("received %s %s from %s", m.Op(), m.Message(), m.NodeID()) + tc.Outf("received %s %s from %s\n", m.Op(), m.Message(), m.NodeID()) genesisPeerMessages.PushRight(m) }), ) @@ -203,7 +203,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ) require.Eventually(func() bool { _, err := client.GetTx(tc.DefaultContext(), txID) - tc.Outf("err: %v", err) return err == nil }, tests.DefaultTimeout, 100*time.Millisecond) }) @@ -263,9 +262,9 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("fetching the subnet conversion attestation", func() { unsignedSubnetConversion := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( networkID, - chainID, + constants.PlatformChainID, must[*payload.AddressedCall](tc)(payload.NewAddressedCall( - address, + nil, must[*warpmessage.SubnetConversion](tc)(warpmessage.NewSubnetConversion( expectedConversionID, )).Bytes(), @@ -392,7 +391,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ) require.Eventually(func() bool { _, err := client.GetTx(tc.DefaultContext(), txID) - tc.Outf("err: %v", err) return err == nil }, tests.DefaultTimeout, 100*time.Millisecond) }) @@ -425,9 +423,9 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("fetching the validator registration attestation", func() { unsignedSubnetValidatorRegistration := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( networkID, - chainID, + constants.PlatformChainID, must[*payload.AddressedCall](tc)(payload.NewAddressedCall( - address, + nil, must[*warpmessage.SubnetValidatorRegistration](tc)(warpmessage.NewSubnetValidatorRegistration( registerValidationID, true, // registered @@ -516,7 +514,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ) require.Eventually(func() bool { _, err := client.GetTx(tc.DefaultContext(), txID) - tc.Outf("err: %v", err) return err == nil }, tests.DefaultTimeout, 100*time.Millisecond) }) @@ -545,9 +542,9 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("fetching the validator removal attestation", func() { unsignedSubnetValidatorRegistration := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( networkID, - chainID, + constants.PlatformChainID, must[*payload.AddressedCall](tc)(payload.NewAddressedCall( - address, + nil, must[*warpmessage.SubnetValidatorRegistration](tc)(warpmessage.NewSubnetValidatorRegistration( registerValidationID, true, // removed From 1f0648989c86e4920796acc259c4a1941a68837c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 14:21:02 -0400 Subject: [PATCH 175/199] fix test --- tests/e2e/p/permissionless_layer_one.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 35f1381cbbde..b092e327302d 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -547,7 +547,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { nil, must[*warpmessage.SubnetValidatorRegistration](tc)(warpmessage.NewSubnetValidatorRegistration( registerValidationID, - true, // removed + false, // removed )).Bytes(), )).Bytes(), )) From b0c0f0f23107a90feac449e5402eb7aed3dc70bc Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 15:18:14 -0400 Subject: [PATCH 176/199] fix error message --- vms/platformvm/txs/executor/standard_tx_executor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 041b0fb25056..0dbf8ae4a1e6 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -719,7 +719,7 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return err } if msg.Expiry > maxAllowedExpiry { - return fmt.Errorf("expected expiry to be before %d but got %d", maxAllowedExpiry, msg.Expiry) + return fmt.Errorf("expected expiry not to be after %d but got %d", maxAllowedExpiry, msg.Expiry) } pop := signer.ProofOfPossession{ From ad50b635f3146db6b3037e579b994b674b8f89d4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 15:33:30 -0400 Subject: [PATCH 177/199] Update the weight --- tests/e2e/p/permissionless_layer_one.go | 106 +++++++++++++----------- 1 file changed, 58 insertions(+), 48 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 459b2fcd9630..21f7a616e419 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -49,6 +49,7 @@ const ( genesisWeight = units.Schmeckle genesisBalance = units.Avax registerWeight = genesisWeight / 10 + updatedWeight = 2 * registerWeight registerBalance = 0 ) @@ -226,22 +227,22 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ) }) - tc.By("verifying the Permissionless L1 reports the correct validator set", func() { + verifyValidatorSet := func(expectedValidators map[ids.NodeID]*snowvalidators.GetValidatorOutput) { height, err := pClient.GetHeight(tc.DefaultContext()) require.NoError(err) subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) require.NoError(err) - require.Equal( - map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, + require.Equal(expectedValidators, subnetValidators) + } + tc.By("verifying the Permissionless L1 reports the correct validator set", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, }, - subnetValidators, - ) + }) }) advanceProposerVMPChainHeight := func() { @@ -339,30 +340,21 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) tc.By("verifying the Permissionless L1 reports the correct validator set", func() { - height, err := pClient.GetHeight(tc.DefaultContext()) - require.NoError(err) - - subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) - require.NoError(err) - require.Equal( - map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, - ids.EmptyNodeID: { // The validator is not active - NodeID: ids.EmptyNodeID, - Weight: registerWeight, - }, + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, }, - subnetValidators, - ) + ids.EmptyNodeID: { // The validator is not active + NodeID: ids.EmptyNodeID, + Weight: registerWeight, + }, + }) }) - tc.By("advancing the proposervm P-chain height", advanceProposerVMPChainHeight) - - tc.By("removing the registered validator", func() { + var nextNonce uint64 + setWeight := func(validationID ids.ID, weight uint64) { tc.By("creating the unsigned warp message") unsignedSubnetValidatorWeight := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( networkID, @@ -370,9 +362,9 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { must[*payload.AddressedCall](tc)(payload.NewAddressedCall( address, must[*warpmessage.SubnetValidatorWeight](tc)(warpmessage.NewSubnetValidatorWeight( - registerValidationID, - 0, // nonce can be anything here - 0, // weight of 0 means the validator is removed + validationID, + nextNonce, + weight, )).Bytes(), )).Bytes(), )) @@ -392,7 +384,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { setSubnetValidatorWeightSignature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) require.True(ok) - tc.By("creating the signed warp message to remove the validator") + tc.By("creating the signed warp message to increase the weight of the validator") signers := set.NewBits() signers.Add(0) // [signers] has weight from the genesis peer @@ -413,24 +405,42 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ) require.NoError(err) }) + + nextNonce++ + } + + tc.By("increasing the weight of the validator", func() { + setWeight(registerValidationID, updatedWeight) }) tc.By("verifying the Permissionless L1 reports the correct validator set", func() { - height, err := pClient.GetHeight(tc.DefaultContext()) - require.NoError(err) + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + ids.EmptyNodeID: { // The validator is not active + NodeID: ids.EmptyNodeID, + Weight: updatedWeight, + }, + }) + }) - subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) - require.NoError(err) - require.Equal( - map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, + tc.By("advancing the proposervm P-chain height", advanceProposerVMPChainHeight) + + tc.By("removing the registered validator", func() { + setWeight(registerValidationID, 0) + }) + + tc.By("verifying the Permissionless L1 reports the correct validator set", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, }, - subnetValidators, - ) + }) }) }) }) From 8950c34adca54a33811fa70b5a6e814b9ee4fefb Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 15:43:57 -0400 Subject: [PATCH 178/199] cleanup --- tests/e2e/p/permissionless_layer_one.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 3f96f5601a42..f3b6629a3211 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -274,7 +274,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("sending the request to sign the warp message", func() { registerSubnetValidatorRequest, err := wrapWarpSignatureRequest( - constants.PlatformChainID, unsignedSubnetConversion, subnetID[:], ) @@ -348,7 +347,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("sending the request to sign the warp message", func() { registerSubnetValidatorRequest, err := wrapWarpSignatureRequest( - chainID, unsignedRegisterSubnetValidator, nil, ) @@ -428,7 +426,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("sending the request to sign the warp message", func() { subnetValidatorRegistrationRequest, err := wrapWarpSignatureRequest( - constants.PlatformChainID, unsignedSubnetValidatorRegistration, nil, ) @@ -464,7 +461,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("sending the request to sign the warp message", func() { setSubnetValidatorWeightRequest, err := wrapWarpSignatureRequest( - chainID, unsignedSubnetValidatorWeight, nil, ) @@ -549,7 +545,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("sending the request to sign the warp message", func() { subnetValidatorRegistrationRequest, err := wrapWarpSignatureRequest( - constants.PlatformChainID, unsignedSubnetValidatorWeight, nil, ) @@ -607,7 +602,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("sending the request to sign the warp message", func() { subnetValidatorRegistrationRequest, err := wrapWarpSignatureRequest( - constants.PlatformChainID, unsignedSubnetValidatorRegistration, justificationBytes, ) @@ -628,7 +622,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) func wrapWarpSignatureRequest( - chainID ids.ID, msg *warp.UnsignedMessage, justification []byte, ) (p2pmessage.OutboundMessage, error) { @@ -652,7 +645,7 @@ func wrapWarpSignatureRequest( } return p2pMessageFactory.AppRequest( - chainID, + msg.SourceChainID, 0, time.Hour, p2psdk.PrefixMessage( From 0e166fc9b863e219b9a6e1ffc7a61f2881c9b1f2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 15:49:01 -0400 Subject: [PATCH 179/199] nit --- tests/e2e/p/permissionless_layer_one.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index f3b6629a3211..7ee457b955fd 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -514,7 +514,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { setWeight(registerValidationID, updatedWeight) }) - tc.By("verifying the validator weight was increase", func() { + tc.By("verifying the validator weight was increased", func() { tc.By("verifying the validator set was updated", func() { verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ subnetGenesisNode.NodeID: { From 5e7be2b40b6fa6b0bbe08a4126275911f9623011 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 15:53:23 -0400 Subject: [PATCH 180/199] fix log --- tests/e2e/p/permissionless_layer_one.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index cf02eb70659f..ef7215af4cfb 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -147,7 +147,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { subnetGenesisNode.StakingAddress, networkID, router.InboundHandlerFunc(func(_ context.Context, m p2pmessage.InboundMessage) { - tc.Outf("received %s %s from %s", m.Op(), m.Message(), m.NodeID()) + tc.Outf("received %s %s from %s\n", m.Op(), m.Message(), m.NodeID()) genesisPeerMessages.PushRight(m) }), ) From 1471214c440c289a1158f7740535dd52ff92db91 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 16:04:50 -0400 Subject: [PATCH 181/199] reduce diff --- tests/e2e/p/permissionless_layer_one.go | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 64ee1da7aa8f..30278b316ad2 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -27,6 +27,8 @@ import ( "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) +const genesisWeight = 100 + var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc := e2e.NewTestContext() require := require.New(tc) @@ -108,19 +110,17 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ) require.NoError(err) - tc.By("creating an ephemeral node") + tc.By("creating the genesis validator") subnetGenesisNode := e2e.AddEphemeralNode(tc, env.GetNetwork(), tmpnet.FlagsMap{ config.TrackSubnetsKey: subnetID.String(), }) - subnetGenesisNodeInfoAPI := info.NewClient(subnetGenesisNode.URI) - nodeID, nodePoP, err := subnetGenesisNodeInfoAPI.GetNodeID(tc.DefaultContext()) + genesisNodePoP, err := subnetGenesisNode.GetProofOfPossession() require.NoError(err) - nodePK, err := bls.PublicKeyFromCompressedBytes(nodePoP.PublicKey[:]) + genesisNodePK, err := bls.PublicKeyFromCompressedBytes(genesisNodePoP.PublicKey[:]) require.NoError(err) - const weight = 100 var ( chainID = chainTx.ID() address = []byte{} @@ -132,10 +132,10 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { address, []*txs.ConvertSubnetValidator{ { - NodeID: nodeID.Bytes(), - Weight: weight, + NodeID: subnetGenesisNode.NodeID.Bytes(), + Weight: genesisWeight, Balance: units.Avax, - Signer: *nodePoP, + Signer: *genesisNodePoP, }, }, tc.WithDefaultContext(), @@ -148,9 +148,9 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ManagerAddress: address, Validators: []message.SubnetConversionValidatorData{ { - NodeID: nodeID.Bytes(), - BLSPublicKey: nodePoP.PublicKey, - Weight: weight, + NodeID: subnetGenesisNode.NodeID.Bytes(), + BLSPublicKey: genesisNodePoP.PublicKey, + Weight: genesisWeight, }, }, }) @@ -183,8 +183,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { map[ids.NodeID]*validators.GetValidatorOutput{ subnetGenesisNode.NodeID: { NodeID: subnetGenesisNode.NodeID, - PublicKey: nodePK, - Weight: weight, + PublicKey: genesisNodePK, + Weight: genesisWeight, }, }, subnetValidators, From cce94e5b6097ce9a4bedd9ff3c3d976ee818e4a0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 16:31:25 -0400 Subject: [PATCH 182/199] update --- tests/e2e/p/permissionless_layer_one.go | 182 +++++++++++++----------- 1 file changed, 98 insertions(+), 84 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 22c4b8f7154a..e7a9b64c61a3 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -5,6 +5,7 @@ package p import ( "context" + "errors" "math" "slices" "time" @@ -31,7 +32,6 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/example/xsvm/genesis" - "github.com/ava-labs/avalanchego/vms/platformvm" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -41,6 +41,7 @@ import ( p2psdk "github.com/ava-labs/avalanchego/network/p2p" p2ppb "github.com/ava-labs/avalanchego/proto/pb/p2p" snowvalidators "github.com/ava-labs/avalanchego/snow/validators" + platformvmsdk "github.com/ava-labs/avalanchego/vms/platformvm" platformvmvalidators "github.com/ava-labs/avalanchego/vms/platformvm/validators" warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" ) @@ -77,7 +78,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { keychain = env.NewKeychain() baseWallet = e2e.NewWallet(tc, keychain, nodeURI) pWallet = baseWallet.P() - pClient = platformvm.NewClient(nodeURI.URI) + pClient = platformvmsdk.NewClient(nodeURI.URI) owner = &secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{ @@ -118,7 +119,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) require.NoError(err) require.Equal( - platformvm.GetSubnetClientResponse{ + platformvmsdk.GetSubnetClientResponse{ IsPermissioned: true, ControlKeys: []ids.ShortID{ keychain.Keys[0].Address(), @@ -195,6 +196,14 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { require.NoError(err) }) + verifyValidatorSet := func(expectedValidators map[ids.NodeID]*snowvalidators.GetValidatorOutput) { + height, err := pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) + + subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) + require.NoError(err) + require.Equal(expectedValidators, subnetValidators) + } tc.By("verifying the Permissioned Subnet was converted to a Permissionless L1", func() { expectedConversionID, err := warpmessage.SubnetConversionID(warpmessage.SubnetConversionData{ SubnetID: subnetID, @@ -210,38 +219,32 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) require.NoError(err) - subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) - require.NoError(err) - require.Equal( - platformvm.GetSubnetClientResponse{ - IsPermissioned: false, - ControlKeys: []ids.ShortID{ - keychain.Keys[0].Address(), + tc.By("verifying the subnet reports as being converted", func() { + subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) + require.NoError(err) + require.Equal( + platformvmsdk.GetSubnetClientResponse{ + IsPermissioned: false, + ControlKeys: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + Threshold: 1, + ConversionID: expectedConversionID, + ManagerChainID: chainID, + ManagerAddress: address, }, - Threshold: 1, - ConversionID: expectedConversionID, - ManagerChainID: chainID, - ManagerAddress: address, - }, - subnet, - ) - }) - - verifyValidatorSet := func(expectedValidators map[ids.NodeID]*snowvalidators.GetValidatorOutput) { - height, err := pClient.GetHeight(tc.DefaultContext()) - require.NoError(err) + subnet, + ) + }) - subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) - require.NoError(err) - require.Equal(expectedValidators, subnetValidators) - } - tc.By("verifying the Permissionless L1 reports the correct validator set", func() { - verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, + tc.By("verifying the validator set was updated", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + }) }) }) @@ -301,7 +304,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("sending the request to sign the warp message", func() { registerSubnetValidatorRequest, err := wrapWarpSignatureRequest( - chainID, unsignedRegisterSubnetValidator, nil, ) @@ -311,7 +313,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) tc.By("getting the signature response") - registerSubnetValidatorSignature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + registerSubnetValidatorSignature, ok, err := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.NoError(err) require.True(ok) tc.By("creating the signed warp message to register the validator") @@ -339,17 +342,19 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) }) - tc.By("verifying the Permissionless L1 reports the correct validator set", func() { - verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, - ids.EmptyNodeID: { // The validator is not active - NodeID: ids.EmptyNodeID, - Weight: registerWeight, - }, + tc.By("verifying the validator was registered", func() { + tc.By("verifying the validator set was updated", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + ids.EmptyNodeID: { // The validator is not active + NodeID: ids.EmptyNodeID, + Weight: registerWeight, + }, + }) }) }) @@ -371,7 +376,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("sending the request to sign the warp message", func() { setSubnetValidatorWeightRequest, err := wrapWarpSignatureRequest( - chainID, unsignedSubnetValidatorWeight, nil, ) @@ -381,7 +385,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) tc.By("getting the signature response") - setSubnetValidatorWeightSignature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + setSubnetValidatorWeightSignature, ok, err := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.NoError(err) require.True(ok) tc.By("creating the signed warp message to increase the weight of the validator") @@ -413,17 +418,19 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { setWeight(registerValidationID, updatedWeight) }) - tc.By("verifying the Permissionless L1 reports the correct validator set", func() { - verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, - ids.EmptyNodeID: { // The validator is not active - NodeID: ids.EmptyNodeID, - Weight: updatedWeight, - }, + tc.By("verifying the validator weight was increased", func() { + tc.By("verifying the validator set was updated", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + ids.EmptyNodeID: { // The validator is not active + NodeID: ids.EmptyNodeID, + Weight: updatedWeight, + }, + }) }) }) @@ -433,20 +440,21 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { setWeight(registerValidationID, 0) }) - tc.By("verifying the Permissionless L1 reports the correct validator set", func() { - verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, + tc.By("verifying the validator was removed", func() { + tc.By("verifying the validator set was updated", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + }) }) }) }) }) func wrapWarpSignatureRequest( - chainID ids.ID, msg *warp.UnsignedMessage, justification []byte, ) (p2pmessage.OutboundMessage, error) { @@ -470,7 +478,7 @@ func wrapWarpSignatureRequest( } return p2pMessageFactory.AppRequest( - chainID, + msg.SourceChainID, 0, time.Hour, p2psdk.PrefixMessage( @@ -482,8 +490,8 @@ func wrapWarpSignatureRequest( func findMessage[T any]( q buffer.BlockingDeque[p2pmessage.InboundMessage], - parser func(p2pmessage.InboundMessage) (T, bool), -) (T, bool) { + parser func(p2pmessage.InboundMessage) (T, bool, error), +) (T, bool, error) { var messagesToReprocess []p2pmessage.InboundMessage defer func() { slices.Reverse(messagesToReprocess) @@ -495,35 +503,41 @@ func findMessage[T any]( for { msg, ok := q.PopLeft() if !ok { - return utils.Zero[T](), false + return utils.Zero[T](), false, nil } - parsed, ok := parser(msg) + parsed, ok, err := parser(msg) + if err != nil { + return utils.Zero[T](), false, err + } if ok { - return parsed, true + return parsed, true, nil } messagesToReprocess = append(messagesToReprocess, msg) } } -func unwrapWarpSignature(msg p2pmessage.InboundMessage) (*bls.Signature, bool) { - appResponse, ok := msg.Message().(*p2ppb.AppResponse) - if !ok { - return nil, false +// unwrapWarpSignature assumes the only type of AppResponses that will be +// received are ACP-118 compliant responses. +func unwrapWarpSignature(msg p2pmessage.InboundMessage) (*bls.Signature, bool, error) { + var appResponse *p2ppb.AppResponse + switch msg := msg.Message().(type) { + case *p2ppb.AppResponse: + appResponse = msg + case *p2ppb.AppError: + return nil, false, errors.New(msg.ErrorMessage) + default: + return nil, false, nil } var response sdk.SignatureResponse - err := proto.Unmarshal(appResponse.AppBytes, &response) - if err != nil { - return nil, false + if err := proto.Unmarshal(appResponse.AppBytes, &response); err != nil { + return nil, false, err } warpSignature, err := bls.SignatureFromBytes(response.Signature) - if err != nil { - return nil, false - } - return warpSignature, true + return warpSignature, true, err } func must[T any](t require.TestingT) func(T, error) T { From 21d732ba80be144bfd1bb67d8256d7e853ac7a27 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 16:36:50 -0400 Subject: [PATCH 183/199] backport tests --- tests/e2e/p/permissionless_layer_one.go | 182 +++++++++++++----------- 1 file changed, 98 insertions(+), 84 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 22c4b8f7154a..e7a9b64c61a3 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -5,6 +5,7 @@ package p import ( "context" + "errors" "math" "slices" "time" @@ -31,7 +32,6 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/example/xsvm/genesis" - "github.com/ava-labs/avalanchego/vms/platformvm" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -41,6 +41,7 @@ import ( p2psdk "github.com/ava-labs/avalanchego/network/p2p" p2ppb "github.com/ava-labs/avalanchego/proto/pb/p2p" snowvalidators "github.com/ava-labs/avalanchego/snow/validators" + platformvmsdk "github.com/ava-labs/avalanchego/vms/platformvm" platformvmvalidators "github.com/ava-labs/avalanchego/vms/platformvm/validators" warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" ) @@ -77,7 +78,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { keychain = env.NewKeychain() baseWallet = e2e.NewWallet(tc, keychain, nodeURI) pWallet = baseWallet.P() - pClient = platformvm.NewClient(nodeURI.URI) + pClient = platformvmsdk.NewClient(nodeURI.URI) owner = &secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{ @@ -118,7 +119,7 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) require.NoError(err) require.Equal( - platformvm.GetSubnetClientResponse{ + platformvmsdk.GetSubnetClientResponse{ IsPermissioned: true, ControlKeys: []ids.ShortID{ keychain.Keys[0].Address(), @@ -195,6 +196,14 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { require.NoError(err) }) + verifyValidatorSet := func(expectedValidators map[ids.NodeID]*snowvalidators.GetValidatorOutput) { + height, err := pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) + + subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) + require.NoError(err) + require.Equal(expectedValidators, subnetValidators) + } tc.By("verifying the Permissioned Subnet was converted to a Permissionless L1", func() { expectedConversionID, err := warpmessage.SubnetConversionID(warpmessage.SubnetConversionData{ SubnetID: subnetID, @@ -210,38 +219,32 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) require.NoError(err) - subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) - require.NoError(err) - require.Equal( - platformvm.GetSubnetClientResponse{ - IsPermissioned: false, - ControlKeys: []ids.ShortID{ - keychain.Keys[0].Address(), + tc.By("verifying the subnet reports as being converted", func() { + subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) + require.NoError(err) + require.Equal( + platformvmsdk.GetSubnetClientResponse{ + IsPermissioned: false, + ControlKeys: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + Threshold: 1, + ConversionID: expectedConversionID, + ManagerChainID: chainID, + ManagerAddress: address, }, - Threshold: 1, - ConversionID: expectedConversionID, - ManagerChainID: chainID, - ManagerAddress: address, - }, - subnet, - ) - }) - - verifyValidatorSet := func(expectedValidators map[ids.NodeID]*snowvalidators.GetValidatorOutput) { - height, err := pClient.GetHeight(tc.DefaultContext()) - require.NoError(err) + subnet, + ) + }) - subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) - require.NoError(err) - require.Equal(expectedValidators, subnetValidators) - } - tc.By("verifying the Permissionless L1 reports the correct validator set", func() { - verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, + tc.By("verifying the validator set was updated", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + }) }) }) @@ -301,7 +304,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("sending the request to sign the warp message", func() { registerSubnetValidatorRequest, err := wrapWarpSignatureRequest( - chainID, unsignedRegisterSubnetValidator, nil, ) @@ -311,7 +313,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) tc.By("getting the signature response") - registerSubnetValidatorSignature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + registerSubnetValidatorSignature, ok, err := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.NoError(err) require.True(ok) tc.By("creating the signed warp message to register the validator") @@ -339,17 +342,19 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) }) - tc.By("verifying the Permissionless L1 reports the correct validator set", func() { - verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, - ids.EmptyNodeID: { // The validator is not active - NodeID: ids.EmptyNodeID, - Weight: registerWeight, - }, + tc.By("verifying the validator was registered", func() { + tc.By("verifying the validator set was updated", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + ids.EmptyNodeID: { // The validator is not active + NodeID: ids.EmptyNodeID, + Weight: registerWeight, + }, + }) }) }) @@ -371,7 +376,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc.By("sending the request to sign the warp message", func() { setSubnetValidatorWeightRequest, err := wrapWarpSignatureRequest( - chainID, unsignedSubnetValidatorWeight, nil, ) @@ -381,7 +385,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) tc.By("getting the signature response") - setSubnetValidatorWeightSignature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) + setSubnetValidatorWeightSignature, ok, err := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.NoError(err) require.True(ok) tc.By("creating the signed warp message to increase the weight of the validator") @@ -413,17 +418,19 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { setWeight(registerValidationID, updatedWeight) }) - tc.By("verifying the Permissionless L1 reports the correct validator set", func() { - verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, - ids.EmptyNodeID: { // The validator is not active - NodeID: ids.EmptyNodeID, - Weight: updatedWeight, - }, + tc.By("verifying the validator weight was increased", func() { + tc.By("verifying the validator set was updated", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + ids.EmptyNodeID: { // The validator is not active + NodeID: ids.EmptyNodeID, + Weight: updatedWeight, + }, + }) }) }) @@ -433,20 +440,21 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { setWeight(registerValidationID, 0) }) - tc.By("verifying the Permissionless L1 reports the correct validator set", func() { - verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, + tc.By("verifying the validator was removed", func() { + tc.By("verifying the validator set was updated", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + }) }) }) }) }) func wrapWarpSignatureRequest( - chainID ids.ID, msg *warp.UnsignedMessage, justification []byte, ) (p2pmessage.OutboundMessage, error) { @@ -470,7 +478,7 @@ func wrapWarpSignatureRequest( } return p2pMessageFactory.AppRequest( - chainID, + msg.SourceChainID, 0, time.Hour, p2psdk.PrefixMessage( @@ -482,8 +490,8 @@ func wrapWarpSignatureRequest( func findMessage[T any]( q buffer.BlockingDeque[p2pmessage.InboundMessage], - parser func(p2pmessage.InboundMessage) (T, bool), -) (T, bool) { + parser func(p2pmessage.InboundMessage) (T, bool, error), +) (T, bool, error) { var messagesToReprocess []p2pmessage.InboundMessage defer func() { slices.Reverse(messagesToReprocess) @@ -495,35 +503,41 @@ func findMessage[T any]( for { msg, ok := q.PopLeft() if !ok { - return utils.Zero[T](), false + return utils.Zero[T](), false, nil } - parsed, ok := parser(msg) + parsed, ok, err := parser(msg) + if err != nil { + return utils.Zero[T](), false, err + } if ok { - return parsed, true + return parsed, true, nil } messagesToReprocess = append(messagesToReprocess, msg) } } -func unwrapWarpSignature(msg p2pmessage.InboundMessage) (*bls.Signature, bool) { - appResponse, ok := msg.Message().(*p2ppb.AppResponse) - if !ok { - return nil, false +// unwrapWarpSignature assumes the only type of AppResponses that will be +// received are ACP-118 compliant responses. +func unwrapWarpSignature(msg p2pmessage.InboundMessage) (*bls.Signature, bool, error) { + var appResponse *p2ppb.AppResponse + switch msg := msg.Message().(type) { + case *p2ppb.AppResponse: + appResponse = msg + case *p2ppb.AppError: + return nil, false, errors.New(msg.ErrorMessage) + default: + return nil, false, nil } var response sdk.SignatureResponse - err := proto.Unmarshal(appResponse.AppBytes, &response) - if err != nil { - return nil, false + if err := proto.Unmarshal(appResponse.AppBytes, &response); err != nil { + return nil, false, err } warpSignature, err := bls.SignatureFromBytes(response.Signature) - if err != nil { - return nil, false - } - return warpSignature, true + return warpSignature, true, err } func must[T any](t require.TestingT) func(T, error) T { From afeabb608f9b0bbb200bc036bbc55dd2b8636c29 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 16:38:00 -0400 Subject: [PATCH 184/199] backport tests --- tests/e2e/p/permissionless_layer_one.go | 463 +++++++++++++----------- 1 file changed, 246 insertions(+), 217 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 7eccfdd6965e..a583789ecf01 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -5,6 +5,7 @@ package p import ( "context" + "errors" "math" "slices" "time" @@ -31,7 +32,6 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/example/xsvm/genesis" - "github.com/ava-labs/avalanchego/vms/platformvm" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -41,44 +41,53 @@ import ( p2psdk "github.com/ava-labs/avalanchego/network/p2p" p2ppb "github.com/ava-labs/avalanchego/proto/pb/p2p" snowvalidators "github.com/ava-labs/avalanchego/snow/validators" + platformvmsdk "github.com/ava-labs/avalanchego/vms/platformvm" platformvmvalidators "github.com/ava-labs/avalanchego/vms/platformvm/validators" warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" ) -const genesisWeight = 100 +const ( + genesisWeight = units.Schmeckle + genesisBalance = units.Avax + registerWeight = genesisWeight / 10 + updatedWeight = 2 * registerWeight + registerBalance = 0 +) var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc := e2e.NewTestContext() require := require.New(tc) - ginkgo.It("creates a Permissionless L1", func() { + ginkgo.It("creates and updates Permissionless L1", func() { env := e2e.GetEnv(tc) nodeURI := env.GetRandomNodeURI() - infoClient := info.NewClient(nodeURI.URI) - - tc.By("fetching upgrade config") - upgrades, err := infoClient.Upgrades(tc.DefaultContext()) - require.NoError(err) - tc.By("verifying Etna is activated") - now := time.Now() - if !upgrades.IsEtnaActivated(now) { - ginkgo.Skip("Etna is not activated. Permissionless L1s are enabled post-Etna, skipping test.") - } - - keychain := env.NewKeychain() - baseWallet := e2e.NewWallet(tc, keychain, nodeURI) + tc.By("verifying Etna is activated", func() { + infoClient := info.NewClient(nodeURI.URI) + upgrades, err := infoClient.Upgrades(tc.DefaultContext()) + require.NoError(err) - pWallet := baseWallet.P() - pClient := platformvm.NewClient(nodeURI.URI) + now := time.Now() + if !upgrades.IsEtnaActivated(now) { + ginkgo.Skip("Etna is not activated. Permissionless L1s are enabled post-Etna, skipping test.") + } + }) - owner := &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ - keychain.Keys[0].Address(), - }, - } + tc.By("loading the wallet") + var ( + keychain = env.NewKeychain() + baseWallet = e2e.NewWallet(tc, keychain, nodeURI) + pWallet = baseWallet.P() + pClient = platformvmsdk.NewClient(nodeURI.URI) + owner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + } + ) + tc.By("creating the chain genesis") genesisKey, err := secp256k1.NewPrivateKey() require.NoError(err) @@ -93,40 +102,48 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) require.NoError(err) - tc.By("issuing a CreateSubnetTx") - subnetTx, err := pWallet.IssueCreateSubnetTx( - owner, - tc.WithDefaultContext(), - ) - require.NoError(err) + var subnetID ids.ID + tc.By("issuing a CreateSubnetTx", func() { + subnetTx, err := pWallet.IssueCreateSubnetTx( + owner, + tc.WithDefaultContext(), + ) + require.NoError(err) - tc.By("verifying a Permissioned Subnet was successfully created") - subnetID := subnetTx.ID() - require.NotEqual(subnetID, constants.PrimaryNetworkID) + subnetID = subnetTx.ID() + }) - res, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) - require.NoError(err) - require.Equal( - platformvm.GetSubnetClientResponse{ - IsPermissioned: true, - ControlKeys: []ids.ShortID{ - keychain.Keys[0].Address(), + tc.By("verifying a Permissioned Subnet was successfully created", func() { + require.NotEqual(constants.PrimaryNetworkID, subnetID) + + subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) + require.NoError(err) + require.Equal( + platformvmsdk.GetSubnetClientResponse{ + IsPermissioned: true, + ControlKeys: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + Threshold: 1, }, - Threshold: 1, - }, - res, - ) + subnet, + ) + }) - tc.By("issuing a CreateChainTx") - chainTx, err := pWallet.IssueCreateChainTx( - subnetID, - genesisBytes, - constants.XSVMID, - nil, - "No Permissions", - tc.WithDefaultContext(), - ) - require.NoError(err) + var chainID ids.ID + tc.By("issuing a CreateChainTx", func() { + chainTx, err := pWallet.IssueCreateChainTx( + subnetID, + genesisBytes, + constants.XSVMID, + nil, + "No Permissions", + tc.WithDefaultContext(), + ) + require.NoError(err) + + chainID = chainTx.ID() + }) tc.By("creating the genesis validator") subnetGenesisNode := e2e.AddEphemeralNode(tc, env.GetNetwork(), tmpnet.FlagsMap{ @@ -155,90 +172,98 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { ) require.NoError(err) defer func() { + genesisPeerMessages.Close() genesisPeer.StartClose() require.NoError(genesisPeer.AwaitClosed(tc.DefaultContext())) }() - var ( - chainID = chainTx.ID() - address = []byte{} - ) - tc.By("issuing a ConvertSubnetTx") - _, err = pWallet.IssueConvertSubnetTx( - subnetID, - chainID, - address, - []*txs.ConvertSubnetValidator{ - { - NodeID: subnetGenesisNode.NodeID.Bytes(), - Weight: genesisWeight, - Balance: units.Avax, - Signer: *genesisNodePoP, - }, - }, - tc.WithDefaultContext(), - ) - require.NoError(err) - - expectedConversionID, err := warpmessage.SubnetConversionID(warpmessage.SubnetConversionData{ - SubnetID: subnetID, - ManagerChainID: chainID, - ManagerAddress: address, - Validators: []warpmessage.SubnetConversionValidatorData{ - { - NodeID: subnetGenesisNode.NodeID.Bytes(), - BLSPublicKey: genesisNodePoP.PublicKey, - Weight: genesisWeight, + address := []byte{} + tc.By("issuing a ConvertSubnetTx", func() { + _, err := pWallet.IssueConvertSubnetTx( + subnetID, + chainID, + address, + []*txs.ConvertSubnetValidator{ + { + NodeID: subnetGenesisNode.NodeID.Bytes(), + Weight: genesisWeight, + Balance: genesisBalance, + Signer: *genesisNodePoP, + }, }, - }, + tc.WithDefaultContext(), + ) + require.NoError(err) }) - require.NoError(err) - tc.By("waiting to update the proposervm P-chain height") - time.Sleep((5 * platformvmvalidators.RecentlyAcceptedWindowTTL) / 4) + verifyValidatorSet := func(expectedValidators map[ids.NodeID]*snowvalidators.GetValidatorOutput) { + height, err := pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) - tc.By("issuing random transactions to update the proposervm P-chain height") - for range 2 { - _, err = pWallet.IssueCreateSubnetTx( - owner, - tc.WithDefaultContext(), - ) + subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) require.NoError(err) + require.Equal(expectedValidators, subnetValidators) } - - tc.By("verifying the Permissioned Subnet was converted to a Permissionless L1") - res, err = pClient.GetSubnet(tc.DefaultContext(), subnetID) - require.NoError(err) - require.Equal( - platformvm.GetSubnetClientResponse{ - IsPermissioned: false, - ControlKeys: []ids.ShortID{ - keychain.Keys[0].Address(), - }, - Threshold: 1, - ConversionID: expectedConversionID, + tc.By("verifying the Permissioned Subnet was converted to a Permissionless L1", func() { + expectedConversionID, err := warpmessage.SubnetConversionID(warpmessage.SubnetConversionData{ + SubnetID: subnetID, ManagerChainID: chainID, ManagerAddress: address, - }, - res, - ) + Validators: []warpmessage.SubnetConversionValidatorData{ + { + NodeID: subnetGenesisNode.NodeID.Bytes(), + BLSPublicKey: genesisNodePoP.PublicKey, + Weight: genesisWeight, + }, + }, + }) + require.NoError(err) - tc.By("verifying the Permissionless L1 reports the correct validator set") - height, err := pClient.GetHeight(tc.DefaultContext()) - require.NoError(err) + tc.By("verifying the subnet reports as being converted", func() { + subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) + require.NoError(err) + require.Equal( + platformvmsdk.GetSubnetClientResponse{ + IsPermissioned: false, + ControlKeys: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + Threshold: 1, + ConversionID: expectedConversionID, + ManagerChainID: chainID, + ManagerAddress: address, + }, + subnet, + ) + }) + + tc.By("verifying the validator set was updated", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + }) + }) + }) - subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) - require.NoError(err) - require.Equal( - map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, - }, - subnetValidators, - ) + advanceProposerVMPChainHeight := func() { + // We first must wait at least [RecentlyAcceptedWindowTTL] to ensure + // the next block will evict the prior block from the windower. + time.Sleep((5 * platformvmvalidators.RecentlyAcceptedWindowTTL) / 4) + + // Now we must: + // 1. issue a block which should include the old P-chain height. + // 2. issue a block which should include the new P-chain height. + for range 2 { + _, err = pWallet.IssueBaseTx(nil, tc.WithDefaultContext()) + require.NoError(err) + } + // Now that a block has been issued with the new P-chain height, the + // next block will use that height for warp message verification. + } + tc.By("advancing the proposervm P-chain height", advanceProposerVMPChainHeight) tc.By("creating the validator to register") subnetRegisterNode := e2e.AddEphemeralNode(tc, env.GetNetwork(), tmpnet.FlagsMap{ @@ -248,95 +273,93 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { registerNodePoP, err := subnetRegisterNode.GetProofOfPossession() require.NoError(err) - registerNodePK, err := bls.PublicKeyFromCompressedBytes(registerNodePoP.PublicKey[:]) - require.NoError(err) - - tc.By("ensures the subnet nodes are healthy") - e2e.WaitForHealthy(tc, subnetGenesisNode) - e2e.WaitForHealthy(tc, subnetRegisterNode) - - const registerWeight = 1 - tc.By("create the unsigned warp message to register the validator") - unsignedRegisterSubnetValidator := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( - networkID, - chainID, - must[*payload.AddressedCall](tc)(payload.NewAddressedCall( - address, - must[*warpmessage.RegisterSubnetValidator](tc)(warpmessage.NewRegisterSubnetValidator( - subnetID, - subnetRegisterNode.NodeID, - registerNodePoP.PublicKey, - uint64(time.Now().Add(5*time.Minute).Unix()), - warpmessage.PChainOwner{}, - warpmessage.PChainOwner{}, - registerWeight, // weight - )).Bytes(), - )).Bytes(), - )) + tc.By("ensuring the subnet nodes are healthy", func() { + e2e.WaitForHealthy(tc, subnetGenesisNode) + e2e.WaitForHealthy(tc, subnetRegisterNode) + }) - registerSubnetValidatorRequest, err := wrapWarpSignatureRequest( - chainID, - unsignedRegisterSubnetValidator, - nil, + tc.By("creating the RegisterSubnetValidatorMessage") + registerSubnetValidatorMessage, err := warpmessage.NewRegisterSubnetValidator( + subnetID, + subnetRegisterNode.NodeID, + registerNodePoP.PublicKey, + uint64(time.Now().Add(5*time.Minute).Unix()), + warpmessage.PChainOwner{}, + warpmessage.PChainOwner{}, + registerWeight, ) require.NoError(err) - tc.By("send the request to sign the warp message") - require.True(genesisPeer.Send(tc.DefaultContext(), registerSubnetValidatorRequest)) + tc.By("registering the validator", func() { + tc.By("creating the unsigned warp message") + unsignedRegisterSubnetValidator := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( + networkID, + chainID, + must[*payload.AddressedCall](tc)(payload.NewAddressedCall( + address, + registerSubnetValidatorMessage.Bytes(), + )).Bytes(), + )) - tc.By("get the signature response") - registerSubnetValidatorSignature, ok := findMessage(genesisPeerMessages, unwrapWarpSignature) - require.True(ok) + tc.By("sending the request to sign the warp message", func() { + registerSubnetValidatorRequest, err := wrapWarpSignatureRequest( + unsignedRegisterSubnetValidator, + nil, + ) + require.NoError(err) - tc.By("create the signed warp message to register the validator") - signers := set.NewBits() - signers.Add(0) // [signers] has weight from the genesis peer + require.True(genesisPeer.Send(tc.DefaultContext(), registerSubnetValidatorRequest)) + }) - var sigBytes [bls.SignatureLen]byte - copy(sigBytes[:], bls.SignatureToBytes(registerSubnetValidatorSignature)) - registerSubnetValidator, err := warp.NewMessage( - unsignedRegisterSubnetValidator, - &warp.BitSetSignature{ - Signers: signers.Bytes(), - Signature: sigBytes, - }, - ) - require.NoError(err) - - tc.By("register the validator") - _, err = pWallet.IssueRegisterSubnetValidatorTx( - 1, - registerNodePoP.ProofOfPossession, - registerSubnetValidator.Bytes(), - ) - require.NoError(err) + tc.By("getting the signature response") + registerSubnetValidatorSignature, ok, err := findMessage(genesisPeerMessages, unwrapWarpSignature) + require.NoError(err) + require.True(ok) + + tc.By("creating the signed warp message to register the validator") + signers := set.NewBits() + signers.Add(0) // [signers] has weight from the genesis peer + + var sigBytes [bls.SignatureLen]byte + copy(sigBytes[:], bls.SignatureToBytes(registerSubnetValidatorSignature)) + registerSubnetValidator, err := warp.NewMessage( + unsignedRegisterSubnetValidator, + &warp.BitSetSignature{ + Signers: signers.Bytes(), + Signature: sigBytes, + }, + ) + require.NoError(err) - tc.By("verify that the validator was registered") - height, err = pClient.GetHeight(tc.DefaultContext()) - require.NoError(err) + tc.By("issuing a RegisterSubnetValidatorTx", func() { + _, err := pWallet.IssueRegisterSubnetValidatorTx( + registerBalance, + registerNodePoP.ProofOfPossession, + registerSubnetValidator.Bytes(), + ) + require.NoError(err) + }) + }) - subnetValidators, err = pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) - require.NoError(err) - require.Equal( - map[ids.NodeID]*snowvalidators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, - }, - subnetRegisterNode.NodeID: { - NodeID: subnetRegisterNode.NodeID, - PublicKey: registerNodePK, - Weight: registerWeight, - }, - }, - subnetValidators, - ) + tc.By("verifying the validator was registered", func() { + tc.By("verifying the validator set was updated", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + ids.EmptyNodeID: { // The validator is not active + NodeID: ids.EmptyNodeID, + Weight: registerWeight, + }, + }) + }) + }) }) }) func wrapWarpSignatureRequest( - chainID ids.ID, msg *warp.UnsignedMessage, justification []byte, ) (p2pmessage.OutboundMessage, error) { @@ -360,7 +383,7 @@ func wrapWarpSignatureRequest( } return p2pMessageFactory.AppRequest( - chainID, + msg.SourceChainID, 0, time.Hour, p2psdk.PrefixMessage( @@ -372,8 +395,8 @@ func wrapWarpSignatureRequest( func findMessage[T any]( q buffer.BlockingDeque[p2pmessage.InboundMessage], - parser func(p2pmessage.InboundMessage) (T, bool), -) (T, bool) { + parser func(p2pmessage.InboundMessage) (T, bool, error), +) (T, bool, error) { var messagesToReprocess []p2pmessage.InboundMessage defer func() { slices.Reverse(messagesToReprocess) @@ -385,35 +408,41 @@ func findMessage[T any]( for { msg, ok := q.PopLeft() if !ok { - return utils.Zero[T](), false + return utils.Zero[T](), false, nil } - parsed, ok := parser(msg) + parsed, ok, err := parser(msg) + if err != nil { + return utils.Zero[T](), false, err + } if ok { - return parsed, true + return parsed, true, nil } messagesToReprocess = append(messagesToReprocess, msg) } } -func unwrapWarpSignature(msg p2pmessage.InboundMessage) (*bls.Signature, bool) { - appResponse, ok := msg.Message().(*p2ppb.AppResponse) - if !ok { - return nil, false +// unwrapWarpSignature assumes the only type of AppResponses that will be +// received are ACP-118 compliant responses. +func unwrapWarpSignature(msg p2pmessage.InboundMessage) (*bls.Signature, bool, error) { + var appResponse *p2ppb.AppResponse + switch msg := msg.Message().(type) { + case *p2ppb.AppResponse: + appResponse = msg + case *p2ppb.AppError: + return nil, false, errors.New(msg.ErrorMessage) + default: + return nil, false, nil } var response sdk.SignatureResponse - err := proto.Unmarshal(appResponse.AppBytes, &response) - if err != nil { - return nil, false + if err := proto.Unmarshal(appResponse.AppBytes, &response); err != nil { + return nil, false, err } warpSignature, err := bls.SignatureFromBytes(response.Signature) - if err != nil { - return nil, false - } - return warpSignature, true + return warpSignature, true, err } func must[T any](t require.TestingT) func(T, error) T { From 39beefb827939de1e40d2931f66d7ec9f99221dd Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 16:39:20 -0400 Subject: [PATCH 185/199] backport tests --- tests/e2e/p/permissionless_layer_one.go | 273 ++++++++++++++---------- 1 file changed, 159 insertions(+), 114 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 30278b316ad2..9489edd5271f 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -4,6 +4,7 @@ package p import ( + "context" "math" "time" @@ -13,54 +14,67 @@ import ( "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/network/peer" + "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/tests/fixture/e2e" "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" + "github.com/ava-labs/avalanchego/utils/buffer" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/example/xsvm/genesis" - "github.com/ava-labs/avalanchego/vms/platformvm" "github.com/ava-labs/avalanchego/vms/platformvm/txs" - "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/secp256k1fx" + + p2pmessage "github.com/ava-labs/avalanchego/message" + snowvalidators "github.com/ava-labs/avalanchego/snow/validators" + platformvmsdk "github.com/ava-labs/avalanchego/vms/platformvm" + warpmessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" ) -const genesisWeight = 100 +const ( + genesisWeight = units.Schmeckle + genesisBalance = units.Avax + registerWeight = genesisWeight / 10 + updatedWeight = 2 * registerWeight + registerBalance = 0 +) var _ = e2e.DescribePChain("[Permissionless L1]", func() { tc := e2e.NewTestContext() require := require.New(tc) - ginkgo.It("creates a Permissionless L1", func() { + ginkgo.It("creates and updates Permissionless L1", func() { env := e2e.GetEnv(tc) nodeURI := env.GetRandomNodeURI() - infoClient := info.NewClient(nodeURI.URI) - tc.By("fetching upgrade config") - upgrades, err := infoClient.Upgrades(tc.DefaultContext()) - require.NoError(err) + tc.By("verifying Etna is activated", func() { + infoClient := info.NewClient(nodeURI.URI) + upgrades, err := infoClient.Upgrades(tc.DefaultContext()) + require.NoError(err) - tc.By("verifying Etna is activated") - now := time.Now() - if !upgrades.IsEtnaActivated(now) { - ginkgo.Skip("Etna is not activated. Permissionless L1s are enabled post-Etna, skipping test.") - } - - keychain := env.NewKeychain() - baseWallet := e2e.NewWallet(tc, keychain, nodeURI) - - pWallet := baseWallet.P() - pClient := platformvm.NewClient(nodeURI.URI) + now := time.Now() + if !upgrades.IsEtnaActivated(now) { + ginkgo.Skip("Etna is not activated. Permissionless L1s are enabled post-Etna, skipping test.") + } + }) - owner := &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ - keychain.Keys[0].Address(), - }, - } + tc.By("loading the wallet") + var ( + keychain = env.NewKeychain() + baseWallet = e2e.NewWallet(tc, keychain, nodeURI) + pWallet = baseWallet.P() + pClient = platformvmsdk.NewClient(nodeURI.URI) + owner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + } + ) + tc.By("creating the chain genesis") genesisKey, err := secp256k1.NewPrivateKey() require.NoError(err) @@ -75,40 +89,48 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) require.NoError(err) - tc.By("issuing a CreateSubnetTx") - subnetTx, err := pWallet.IssueCreateSubnetTx( - owner, - tc.WithDefaultContext(), - ) - require.NoError(err) + var subnetID ids.ID + tc.By("issuing a CreateSubnetTx", func() { + subnetTx, err := pWallet.IssueCreateSubnetTx( + owner, + tc.WithDefaultContext(), + ) + require.NoError(err) - tc.By("verifying a Permissioned Subnet was successfully created") - subnetID := subnetTx.ID() - require.NotEqual(subnetID, constants.PrimaryNetworkID) + subnetID = subnetTx.ID() + }) - res, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) - require.NoError(err) - require.Equal( - platformvm.GetSubnetClientResponse{ - IsPermissioned: true, - ControlKeys: []ids.ShortID{ - keychain.Keys[0].Address(), + tc.By("verifying a Permissioned Subnet was successfully created", func() { + require.NotEqual(constants.PrimaryNetworkID, subnetID) + + subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) + require.NoError(err) + require.Equal( + platformvmsdk.GetSubnetClientResponse{ + IsPermissioned: true, + ControlKeys: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + Threshold: 1, }, - Threshold: 1, - }, - res, - ) + subnet, + ) + }) - tc.By("issuing a CreateChainTx") - chainTx, err := pWallet.IssueCreateChainTx( - subnetID, - genesisBytes, - constants.XSVMID, - nil, - "No Permissions", - tc.WithDefaultContext(), - ) - require.NoError(err) + var chainID ids.ID + tc.By("issuing a CreateChainTx", func() { + chainTx, err := pWallet.IssueCreateChainTx( + subnetID, + genesisBytes, + constants.XSVMID, + nil, + "No Permissions", + tc.WithDefaultContext(), + ) + require.NoError(err) + + chainID = chainTx.ID() + }) tc.By("creating the genesis validator") subnetGenesisNode := e2e.AddEphemeralNode(tc, env.GetNetwork(), tmpnet.FlagsMap{ @@ -121,73 +143,96 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { genesisNodePK, err := bls.PublicKeyFromCompressedBytes(genesisNodePoP.PublicKey[:]) require.NoError(err) + tc.By("connecting to the genesis validator") var ( - chainID = chainTx.ID() - address = []byte{} + networkID = env.GetNetwork().GetNetworkID() + genesisPeerMessages = buffer.NewUnboundedBlockingDeque[p2pmessage.InboundMessage](1) ) - tc.By("issuing a ConvertSubnetTx") - _, err = pWallet.IssueConvertSubnetTx( - subnetID, - chainID, - address, - []*txs.ConvertSubnetValidator{ - { - NodeID: subnetGenesisNode.NodeID.Bytes(), - Weight: genesisWeight, - Balance: units.Avax, - Signer: *genesisNodePoP, - }, - }, - tc.WithDefaultContext(), + genesisPeer, err := peer.StartTestPeer( + tc.DefaultContext(), + subnetGenesisNode.StakingAddress, + networkID, + router.InboundHandlerFunc(func(_ context.Context, m p2pmessage.InboundMessage) { + tc.Outf("received %s %s from %s\n", m.Op(), m.Message(), m.NodeID()) + genesisPeerMessages.PushRight(m) + }), ) require.NoError(err) - - expectedConversionID, err := message.SubnetConversionID(message.SubnetConversionData{ - SubnetID: subnetID, - ManagerChainID: chainID, - ManagerAddress: address, - Validators: []message.SubnetConversionValidatorData{ - { - NodeID: subnetGenesisNode.NodeID.Bytes(), - BLSPublicKey: genesisNodePoP.PublicKey, - Weight: genesisWeight, + defer func() { + genesisPeerMessages.Close() + genesisPeer.StartClose() + require.NoError(genesisPeer.AwaitClosed(tc.DefaultContext())) + }() + + address := []byte{} + tc.By("issuing a ConvertSubnetTx", func() { + _, err := pWallet.IssueConvertSubnetTx( + subnetID, + chainID, + address, + []*txs.ConvertSubnetValidator{ + { + NodeID: subnetGenesisNode.NodeID.Bytes(), + Weight: genesisWeight, + Balance: genesisBalance, + Signer: *genesisNodePoP, + }, }, - }, + tc.WithDefaultContext(), + ) + require.NoError(err) }) - require.NoError(err) - tc.By("verifying the Permissioned Subnet was converted to a Permissionless L1") - res, err = pClient.GetSubnet(tc.DefaultContext(), subnetID) - require.NoError(err) - require.Equal( - platformvm.GetSubnetClientResponse{ - IsPermissioned: false, - ControlKeys: []ids.ShortID{ - keychain.Keys[0].Address(), - }, - Threshold: 1, - ConversionID: expectedConversionID, + verifyValidatorSet := func(expectedValidators map[ids.NodeID]*snowvalidators.GetValidatorOutput) { + height, err := pClient.GetHeight(tc.DefaultContext()) + require.NoError(err) + + subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) + require.NoError(err) + require.Equal(expectedValidators, subnetValidators) + } + tc.By("verifying the Permissioned Subnet was converted to a Permissionless L1", func() { + expectedConversionID, err := warpmessage.SubnetConversionID(warpmessage.SubnetConversionData{ + SubnetID: subnetID, ManagerChainID: chainID, ManagerAddress: address, - }, - res, - ) - - tc.By("verifying the Permissionless L1 reports the correct validator set") - height, err := pClient.GetHeight(tc.DefaultContext()) - require.NoError(err) - - subnetValidators, err := pClient.GetValidatorsAt(tc.DefaultContext(), subnetID, height) - require.NoError(err) - require.Equal( - map[ids.NodeID]*validators.GetValidatorOutput{ - subnetGenesisNode.NodeID: { - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - Weight: genesisWeight, + Validators: []warpmessage.SubnetConversionValidatorData{ + { + NodeID: subnetGenesisNode.NodeID.Bytes(), + BLSPublicKey: genesisNodePoP.PublicKey, + Weight: genesisWeight, + }, }, - }, - subnetValidators, - ) + }) + require.NoError(err) + + tc.By("verifying the subnet reports as being converted", func() { + subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) + require.NoError(err) + require.Equal( + platformvmsdk.GetSubnetClientResponse{ + IsPermissioned: false, + ControlKeys: []ids.ShortID{ + keychain.Keys[0].Address(), + }, + Threshold: 1, + ConversionID: expectedConversionID, + ManagerChainID: chainID, + ManagerAddress: address, + }, + subnet, + ) + }) + + tc.By("verifying the validator set was updated", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{ + subnetGenesisNode.NodeID: { + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + Weight: genesisWeight, + }, + }) + }) + }) }) }) From 19d4c35a752307df039b474c69e20046478d65d9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 16:56:04 -0400 Subject: [PATCH 186/199] nit cleanup --- tests/e2e/p/permissionless_layer_one.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 9489edd5271f..4efbe74800b6 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -34,11 +34,8 @@ import ( ) const ( - genesisWeight = units.Schmeckle - genesisBalance = units.Avax - registerWeight = genesisWeight / 10 - updatedWeight = 2 * registerWeight - registerBalance = 0 + genesisWeight = units.Schmeckle + genesisBalance = units.Avax ) var _ = e2e.DescribePChain("[Permissionless L1]", func() { From ffd6a3ab3da1b8933da3bb948a347354453ce0e1 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 16:59:48 -0400 Subject: [PATCH 187/199] merge --- tests/e2e/p/permissionless_layer_one.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 98e5c22c4477..650fb1050729 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -47,8 +47,10 @@ import ( ) const ( - genesisWeight = units.Schmeckle - genesisBalance = units.Avax + genesisWeight = units.Schmeckle + genesisBalance = units.Avax + registerWeight = genesisWeight / 10 + registerBalance = 0 ) var _ = e2e.DescribePChain("[Permissionless L1]", func() { From 99c24ad69b4e6430ad134f65b218d1b164cf7f65 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 17:01:13 -0400 Subject: [PATCH 188/199] merge --- tests/e2e/p/permissionless_layer_one.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 8879ad534559..e7a9b64c61a3 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -50,6 +50,7 @@ const ( genesisWeight = units.Schmeckle genesisBalance = units.Avax registerWeight = genesisWeight / 10 + updatedWeight = 2 * registerWeight registerBalance = 0 ) From ab85ce1b878426fe907e2031384c7694be13ba1b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 18:06:30 -0400 Subject: [PATCH 189/199] Add ConvertSubnetTx complexity tests --- vms/platformvm/txs/fee/calculator_test.go | 12 ++++++++++++ vms/platformvm/txs/fee/complexity.go | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/vms/platformvm/txs/fee/calculator_test.go b/vms/platformvm/txs/fee/calculator_test.go index 843f20d13e9d..5039dc557345 100644 --- a/vms/platformvm/txs/fee/calculator_test.go +++ b/vms/platformvm/txs/fee/calculator_test.go @@ -219,5 +219,17 @@ var ( }, expectedDynamicFee: 173_600, }, + { + name: "ConvertSubnetTx", + tx: "00000000002300003039000000000000000000000000000000000000000000000000000000000000000000000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000007002386f234262960000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c00000001705f3d4415f990225d3df5ce437d7af2aa324b1bbce854ee34ab6f39882250d200000000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000005002386f26fc0f94e000000010000000000000000a0673b4ee5ec44e57c8ab250dd7cd7b68d04421f64bd6559a4284a3ee358ff2b705f3d4415f990225d3df5ce437d7af2aa324b1bbce854ee34ab6f39882250d2000000000000000100000014c582872c37c81efa2c94ea347af49cdc23a830aa000000000000c137000000003b9aca00a3783a891cb41cadbfcf456da149f30e7af972677a162b984bef0779f254baac51ec042df1781d1295df80fb41c801269731fc6c25e1e5940dc3cb8509e30348fa712742cfdc83678acc9f95908eb98b89b28802fb559b4a2a6ff3216707c07f0ceb0b45a95f4f9a9540bbd3331d8ab4f233bffa4abb97fad9d59a1695f31b92a2b89e365facf7ab8c30de7c4a496d1e000000000000000000000000000000000000000a00000001000000000000000200000009000000011430759900fdf516cdeff6a1390dd7438585568a89c06142c44b3bf1178c4cae4bff44e955b19da08f0359d396a7a738b989bb46377e7465cd858ddd1e8dd3790100000009000000011430759900fdf516cdeff6a1390dd7438585568a89c06142c44b3bf1178c4cae4bff44e955b19da08f0359d396a7a738b989bb46377e7465cd858ddd1e8dd37901", + expectedStaticFeeErr: ErrUnsupportedTx, + expectedComplexity: gas.Dimensions{ + gas.Bandwidth: 656, // The length of the tx in bytes + gas.DBRead: IntrinsicConvertSubnetTxComplexities[gas.DBRead] + intrinsicInputDBRead, + gas.DBWrite: IntrinsicConvertSubnetTxComplexities[gas.DBWrite] + intrinsicInputDBWrite + intrinsicOutputDBWrite + intrinsicConvertSubnetValidatorDBWrite, + gas.Compute: 0, // TODO: implement + }, + expectedDynamicFee: 365_600, + }, } ) diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 7f7de85efe4d..5683ac6b3b6a 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -63,7 +63,6 @@ const ( secp256k1.SignatureLen // signature length intrinsicConvertSubnetValidatorBandwidth = wrappers.IntLen + // nodeID length - ids.NodeIDLen + // nodeID wrappers.LongLen + // weight wrappers.LongLen + // balance wrappers.IntLen + // remaining balance owner threshold @@ -353,6 +352,9 @@ func convertSubnetValidatorComplexity(sov *txs.ConvertSubnetValidator) (gas.Dime return gas.Dimensions{}, err } return complexity.Add( + &gas.Dimensions{ + gas.Bandwidth: uint64(len(sov.NodeID)), + }, &signerComplexity, &gas.Dimensions{ gas.Bandwidth: addressBandwidth, From 1815f648cf0b8723417e9f0be6421af1f8b6054d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 18:07:59 -0400 Subject: [PATCH 190/199] Fix diff --- vms/platformvm/txs/fee/complexity.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index ac8f832eb499..1fc00db0311d 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -190,7 +190,7 @@ var ( ids.IDLen + // chainID wrappers.IntLen + // address length wrappers.IntLen + // validators length - bls.SignatureLen + // proofOfPossession + wrappers.IntLen + // subnetAuth typeID wrappers.IntLen, // subnetAuthCredential typeID gas.DBRead: 2, // subnet auth + manager lookup gas.DBWrite: 2, // manager + weight From 0dd0041e15a839c1dfb80dcd17527c661726b779 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 18:08:29 -0400 Subject: [PATCH 191/199] reduce diff --- vms/platformvm/txs/fee/complexity.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 1fc00db0311d..815be12c9fa6 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -194,7 +194,7 @@ var ( wrappers.IntLen, // subnetAuthCredential typeID gas.DBRead: 2, // subnet auth + manager lookup gas.DBWrite: 2, // manager + weight - gas.Compute: 0, // TODO: Add compute complexity (and include the PoP compute) + gas.Compute: 0, } IntrinsicRegisterSubnetValidatorTxComplexities = gas.Dimensions{ gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + From 1f168c7b6e0f2e1e77e451ec1791a355f183039a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 18:20:47 -0400 Subject: [PATCH 192/199] Add RegisterSubnetValidatorTx complexity tests --- vms/platformvm/txs/fee/calculator_test.go | 12 ++++++++++++ vms/platformvm/txs/fee/complexity.go | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/txs/fee/calculator_test.go b/vms/platformvm/txs/fee/calculator_test.go index 5039dc557345..dd71649fecd3 100644 --- a/vms/platformvm/txs/fee/calculator_test.go +++ b/vms/platformvm/txs/fee/calculator_test.go @@ -231,5 +231,17 @@ var ( }, expectedDynamicFee: 365_600, }, + { + name: "RegisterSubnetValidatorTx", + tx: "00000000002400003039000000000000000000000000000000000000000000000000000000000000000000000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000007002386f1f88b552a000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c00000001ca44ad45a63381b07074be7f82005c41550c989b967f40020f3bedc4b02191f300000000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000005002386f234262404000000010000000000000000000000003b9aca00ab5cb0516b7afdb13727f766185b2b8da44e2653eef63c85f196701083e649289cce1a23c39eb471b2473bc6872aa3ea190de0fe66296cbdd4132c92c3430ff22f28f0b341b15905a005bbd66cc0f4056bc4be5934e4f3a57151a60060f429190000012f000000003039705f3d4415f990225d3df5ce437d7af2aa324b1bbce854ee34ab6f39882250d20000009c000000000001000000000000008e000000000001a0673b4ee5ec44e57c8ab250dd7cd7b68d04421f64bd6559a4284a3ee358ff2b000000145efc86a11c5b12cc95b2cf527c023f9cf6e0e8f6b62034315c5d11cea4190f6ea8997821c02483d29adb5e4567843f7a44c39b2ffa20c8520dc358702fb1ec29f2746dcc000000006705af280000000000000000000000000000000000000000000000010000000000000001018e99dc6ed736089c03b9a1275e0cf801524ed341fb10111f29c0390fa2f96cf6aa78539ec767e5cd523c606c7ede50e60ba6065a3685e770d979b0df74e3541b61ed63f037463776098576e385767a695de59352b44e515831c5ee7a8cc728f9000000010000000900000001a0950b9e6e866130f0d09e2a7bfdd0246513295237258afa942b1850dab79824605c796bbfc9223cf91935fb29c66f8b927690220b9b1c24d6f078054a3e346201", + expectedStaticFeeErr: ErrUnsupportedTx, + expectedComplexity: gas.Dimensions{ + gas.Bandwidth: 710, // The length of the tx in bytes + gas.DBRead: IntrinsicRegisterSubnetValidatorTxComplexities[gas.DBRead] + intrinsicInputDBRead, + gas.DBWrite: IntrinsicRegisterSubnetValidatorTxComplexities[gas.DBWrite] + intrinsicInputDBWrite + intrinsicOutputDBWrite, + gas.Compute: 0, // TODO: implement + }, + expectedDynamicFee: 151_000, + }, } ) diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 815be12c9fa6..0c553f488bb7 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -199,11 +199,11 @@ var ( IntrinsicRegisterSubnetValidatorTxComplexities = gas.Dimensions{ gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + wrappers.LongLen + // balance - wrappers.IntLen + // signer typeID + bls.SignatureLen + // proof of possession wrappers.IntLen, // message length gas.DBRead: 0, // TODO gas.DBWrite: 0, // TODO - gas.Compute: 0, + gas.Compute: 0, // TODO: Include PoP verification time } errUnsupportedOutput = errors.New("unsupported output type") @@ -712,7 +712,7 @@ func (c *complexityVisitor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVali if err != nil { return err } - c.output, err = IntrinsicConvertSubnetTxComplexities.Add( + c.output, err = IntrinsicRegisterSubnetValidatorTxComplexities.Add( &baseTxComplexity, &warpComplexity, ) From ae5c7128dc3df3d274708798dad4c74d4baac7fd Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 18:29:19 -0400 Subject: [PATCH 193/199] Add SetSubnetValidatorWeightTx complexity tests --- vms/platformvm/txs/fee/calculator_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vms/platformvm/txs/fee/calculator_test.go b/vms/platformvm/txs/fee/calculator_test.go index dd71649fecd3..9a3a3f2d1508 100644 --- a/vms/platformvm/txs/fee/calculator_test.go +++ b/vms/platformvm/txs/fee/calculator_test.go @@ -243,5 +243,17 @@ var ( }, expectedDynamicFee: 151_000, }, + { + name: "SetSubnetValidatorWeightTx", + tx: "00000000002500003039000000000000000000000000000000000000000000000000000000000000000000000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000007002386f1f88b5100000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c00000001389c41b6ed301e4c118bd23673268fd2054b772efcf25685a117b74bab7ae5e400000000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000005002386f1f88b552a000000010000000000000000000000d7000000003039705f3d4415f990225d3df5ce437d7af2aa324b1bbce854ee34ab6f39882250d200000044000000000001000000000000003600000000000338e6e9fe31c6d070a8c792dbacf6d0aefb8eac2aded49cc0aa9f422d1fdd9ecd0000000000000001000000000000000500000000000000010187f4bb2c42869c56f023a1ca81045aff034acd490b8f15b5069025f982e605e077007fc588f7d56369a65df7574df3b70ff028ea173739c789525ab7eebfcb5c115b13cca8f02b362104b700c75bc95234109f3f1360ddcb4ec3caf6b0e821cb0000000100000009000000010a29f3c86d52908bf2efbc3f918a363df704c429d66c8d6615712a2a584a2a5f264a9e7b107c07122a06f31cadc2f51285884d36fe8df909a07467417f1d64cf00", + expectedStaticFeeErr: ErrUnsupportedTx, + expectedComplexity: gas.Dimensions{ + gas.Bandwidth: 518, // The length of the tx in bytes + gas.DBRead: IntrinsicSetSubnetValidatorWeightTxComplexities[gas.DBRead] + intrinsicInputDBRead, + gas.DBWrite: IntrinsicSetSubnetValidatorWeightTxComplexities[gas.DBWrite] + intrinsicInputDBWrite + intrinsicOutputDBWrite, + gas.Compute: 0, // TODO: implement + }, + expectedDynamicFee: 131_800, + }, } ) From 1592d2cbb3e55870f589dcd68d7e7c597d017a9e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 18:33:26 -0400 Subject: [PATCH 194/199] Add IncreaseBalanceTx complexity tests --- vms/platformvm/txs/fee/calculator_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vms/platformvm/txs/fee/calculator_test.go b/vms/platformvm/txs/fee/calculator_test.go index 9a3a3f2d1508..cfd17b97330f 100644 --- a/vms/platformvm/txs/fee/calculator_test.go +++ b/vms/platformvm/txs/fee/calculator_test.go @@ -255,5 +255,17 @@ var ( }, expectedDynamicFee: 131_800, }, + { + name: "IncreaseBalanceTx", + tx: "00000000002600003039000000000000000000000000000000000000000000000000000000000000000000000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000007002386f1f88b4e52000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c00000001f61ea7e3bb6d33da9901644f3c623e4537b7d1c276e9ef23bcc8e4150e494d6600000000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000005002386f1f88b510000000001000000000000000038e6e9fe31c6d070a8c792dbacf6d0aefb8eac2aded49cc0aa9f422d1fdd9ecd0000000000000002000000010000000900000001cb56b56387be9186d86430fad5418db4d13e991b6805b6ba178b719e3f47ce001da52d6ed3173bfdd8b69940a135432abce493a10332e881f6c34cea3617595e00", + expectedStaticFeeErr: ErrUnsupportedTx, + expectedComplexity: gas.Dimensions{ + gas.Bandwidth: 339, // The length of the tx in bytes + gas.DBRead: IntrinsicIncreaseBalanceTxComplexities[gas.DBRead] + intrinsicInputDBRead, + gas.DBWrite: IntrinsicIncreaseBalanceTxComplexities[gas.DBWrite] + intrinsicInputDBWrite + intrinsicOutputDBWrite, + gas.Compute: 0, // TODO: implement + }, + expectedDynamicFee: 113_900, + }, } ) From 4bfe1d70d3c87d397b34148e2a278c238e2040a0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 18:39:01 -0400 Subject: [PATCH 195/199] Add DisableSubnetValidatorTx complexity tests --- vms/platformvm/txs/fee/calculator_test.go | 12 ++++++++++++ vms/platformvm/txs/fee/complexity.go | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/vms/platformvm/txs/fee/calculator_test.go b/vms/platformvm/txs/fee/calculator_test.go index cfd17b97330f..c128c278c856 100644 --- a/vms/platformvm/txs/fee/calculator_test.go +++ b/vms/platformvm/txs/fee/calculator_test.go @@ -267,5 +267,17 @@ var ( }, expectedDynamicFee: 113_900, }, + { + name: "DisableSubnetValidatorTx", + tx: "00000000002700003039000000000000000000000000000000000000000000000000000000000000000000000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000007002386f1f88b4b9e000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c00000001fd91c5c421468b13b09dda413bdbe1316c7c9417f2468b893071d4cb608a01da00000000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db00000005002386f1f88b4e5200000001000000000000000038e6e9fe31c6d070a8c792dbacf6d0aefb8eac2aded49cc0aa9f422d1fdd9ecd0000000a00000000000000020000000900000001ff99bb626d898907a660701e2febaa311b4e644fe71add2d1a3f71748102c73f54d73c8370a9ae33e09c984bb8c03da4922bf208af836ec2daaa31cb42788bee010000000900000000", + expectedStaticFeeErr: ErrUnsupportedTx, + expectedComplexity: gas.Dimensions{ + gas.Bandwidth: 347, // The length of the tx in bytes + gas.DBRead: IntrinsicDisableSubnetValidatorTxComplexities[gas.DBRead] + intrinsicInputDBRead, + gas.DBWrite: IntrinsicDisableSubnetValidatorTxComplexities[gas.DBWrite] + intrinsicInputDBWrite + intrinsicOutputDBWrite, + gas.Compute: 0, // TODO: implement + }, + expectedDynamicFee: 114_700, + }, } ) diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 797fad7b566c..1e63e918316b 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -223,7 +223,8 @@ var ( IntrinsicDisableSubnetValidatorTxComplexities = gas.Dimensions{ gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + ids.IDLen + // validationID - wrappers.IntLen, // auth typeID + wrappers.IntLen + // auth typeID + wrappers.IntLen, // authCredential typeID gas.DBRead: 0, // TODO gas.DBWrite: 0, // TODO gas.Compute: 0, From 2b407e9139786e6fb40872170fb6d4c28dfdd0f7 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 19:22:46 -0400 Subject: [PATCH 196/199] Update dynamic fee defaults --- genesis/genesis_fuji.go | 14 +++++++++----- genesis/genesis_local.go | 14 +++++++++----- genesis/genesis_mainnet.go | 14 +++++++++----- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/genesis/genesis_fuji.go b/genesis/genesis_fuji.go index 82a8ffecaa3c..a6b4c4ca960a 100644 --- a/genesis/genesis_fuji.go +++ b/genesis/genesis_fuji.go @@ -42,11 +42,15 @@ var ( gas.DBWrite: 1, gas.Compute: 1, }, - MaxCapacity: 1_000_000, - MaxPerSecond: 1_000, - TargetPerSecond: 500, - MinPrice: 1, - ExcessConversionConstant: 5_000, + MaxCapacity: 1_000_000, // Max block size ~1MB + MaxPerSecond: 250_000, + TargetPerSecond: 125_000, // Target block size ~125KB + MinPrice: 1, + // ExcessConversionConstant = (Capacity - Target) * NumberOfSecondsPerDoubling / ln(2) + // + // ln(2) is a float and the result is consensus critical, so we + // hardcode the result. + ExcessConversionConstant: 5_410_106, // Double every 30s }, ValidatorFeeCapacity: 20_000, ValidatorFeeConfig: validatorfee.Config{ diff --git a/genesis/genesis_local.go b/genesis/genesis_local.go index 5834a366ed2b..166229508f10 100644 --- a/genesis/genesis_local.go +++ b/genesis/genesis_local.go @@ -60,11 +60,15 @@ var ( gas.DBWrite: 1, gas.Compute: 1, }, - MaxCapacity: 1_000_000, - MaxPerSecond: 250_000, - TargetPerSecond: 100_000, - MinPrice: 1, - ExcessConversionConstant: 1_000_000, + MaxCapacity: 1_000_000, // Max block size ~1MB + MaxPerSecond: 250_000, + TargetPerSecond: 125_000, // Target block size ~125KB + MinPrice: 1, + // ExcessConversionConstant = (Capacity - Target) * NumberOfSecondsPerDoubling / ln(2) + // + // ln(2) is a float and the result is consensus critical, so we + // hardcode the result. + ExcessConversionConstant: 5_410_106, // Double every 30s }, ValidatorFeeCapacity: 20_000, ValidatorFeeConfig: validatorfee.Config{ diff --git a/genesis/genesis_mainnet.go b/genesis/genesis_mainnet.go index 2395d88d34b4..dd5b61d481da 100644 --- a/genesis/genesis_mainnet.go +++ b/genesis/genesis_mainnet.go @@ -42,11 +42,15 @@ var ( gas.DBWrite: 1, gas.Compute: 1, }, - MaxCapacity: 1_000_000, - MaxPerSecond: 1_000, - TargetPerSecond: 500, - MinPrice: 1, - ExcessConversionConstant: 5_000, + MaxCapacity: 1_000_000, // Max block size ~1MB + MaxPerSecond: 250_000, + TargetPerSecond: 125_000, // Target block size ~125KB + MinPrice: 1, + // ExcessConversionConstant = (Capacity - Target) * NumberOfSecondsPerDoubling / ln(2) + // + // ln(2) is a float and the result is consensus critical, so we + // hardcode the result. + ExcessConversionConstant: 5_410_106, // Double every 30s }, ValidatorFeeCapacity: 20_000, ValidatorFeeConfig: validatorfee.Config{ From 198680418af3d43b87e76bbad05a2ae2413842f8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 20:44:31 -0400 Subject: [PATCH 197/199] nit cleanup --- vms/platformvm/state/state.go | 16 +++++++++----- vms/platformvm/state/subnet_only_validator.go | 22 +++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 129d210b120c..bede6daaea80 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2261,9 +2261,11 @@ func (s *state) writeSubnetOnlyValidators(updateValidators bool, height uint64) return err } - subnetIDNodeIDKey := make([]byte, len(sov.SubnetID)+len(sov.NodeID)) - copy(subnetIDNodeIDKey, sov.SubnetID[:]) - copy(subnetIDNodeIDKey[len(sov.SubnetID):], sov.NodeID[:]) + subnetIDNodeID := subnetIDNodeID{ + subnetID: sov.SubnetID, + nodeID: sov.NodeID, + } + subnetIDNodeIDKey := subnetIDNodeID.Marshal() if err := s.subnetIDNodeIDDB.Delete(subnetIDNodeIDKey); err != nil { return err } @@ -2375,9 +2377,11 @@ func (s *state) writeSubnetOnlyValidators(updateValidators bool, height uint64) for validationID, sov := range sovChanges { validationID := validationID - subnetIDNodeIDKey := make([]byte, len(sov.SubnetID)+len(sov.NodeID)) - copy(subnetIDNodeIDKey, sov.SubnetID[:]) - copy(subnetIDNodeIDKey[len(sov.SubnetID):], sov.NodeID[:]) + subnetIDNodeID := subnetIDNodeID{ + subnetID: sov.SubnetID, + nodeID: sov.NodeID, + } + subnetIDNodeIDKey := subnetIDNodeID.Marshal() if err := s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, validationID[:]); err != nil { return err } diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 487471a5f2b7..180528bbb9e9 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -18,11 +18,16 @@ import ( safemath "github.com/ava-labs/avalanchego/utils/math" ) +// subnetIDNodeID = [subnetID] + [nodeID] +const subnetIDNodeIDEntryLength = ids.IDLen + ids.NodeIDLen + var ( _ btree.LessFunc[SubnetOnlyValidator] = SubnetOnlyValidator.Less ErrMutatedSubnetOnlyValidator = errors.New("subnet only validator contains mutated constant fields") ErrDuplicateSubnetOnlyValidator = errors.New("subnet only validator contains conflicting subnetID + nodeID pair") + + errUnexpectedSubnetIDNodeIDLength = fmt.Errorf("expected subnetID+nodeID entry length %d", subnetIDNodeIDEntryLength) ) type SubnetOnlyValidators interface { @@ -172,6 +177,23 @@ type subnetIDNodeID struct { nodeID ids.NodeID } +func (s *subnetIDNodeID) Marshal() []byte { + data := make([]byte, subnetIDNodeIDEntryLength) + copy(data, s.subnetID[:]) + copy(data[ids.IDLen:], s.nodeID[:]) + return data +} + +func (s *subnetIDNodeID) Unmarshal(data []byte) error { + if len(data) != subnetIDNodeIDEntryLength { + return errUnexpectedSubnetIDNodeIDLength + } + + copy(s.subnetID[:], data) + copy(s.nodeID[:], data[ids.IDLen:]) + return nil +} + type subnetOnlyValidatorsDiff struct { numAddedActive int // May be negative modifiedTotalWeight map[ids.ID]uint64 // subnetID -> totalWeight From 827997222c6fd4b947d219ec7588b22c95f86be9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 9 Oct 2024 18:38:02 -0400 Subject: [PATCH 198/199] Remove unexpected block unwrapping --- vms/platformvm/block/executor/verifier.go | 56 ++++++++++++++----- .../block/executor/verifier_test.go | 5 +- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index abcbc566a303..38320050af21 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -90,7 +90,8 @@ func (v *verifier) BanffProposalBlock(b *block.BanffProposalBlock) error { } return v.proposalBlock( - &b.ApricotProposalBlock, + b, + b.Tx, onDecisionState, onCommitState, onAbortState, @@ -129,7 +130,12 @@ func (v *verifier) BanffStandardBlock(b *block.BanffStandardBlock) error { } feeCalculator := state.PickFeeCalculator(v.txExecutorBackend.Config, onAcceptState) - return v.standardBlock(&b.ApricotStandardBlock, feeCalculator, onAcceptState) + return v.standardBlock( + b, + b.Transactions, + feeCalculator, + onAcceptState, + ) } func (v *verifier) ApricotAbortBlock(b *block.ApricotAbortBlock) error { @@ -165,7 +171,17 @@ func (v *verifier) ApricotProposalBlock(b *block.ApricotProposalBlock) error { timestamp = onCommitState.GetTimestamp() // Equal to parent timestamp feeCalculator = state.NewStaticFeeCalculator(v.txExecutorBackend.Config, timestamp) ) - return v.proposalBlock(b, nil, onCommitState, onAbortState, feeCalculator, nil, nil, nil) + return v.proposalBlock( + b, + b.Tx, + nil, + onCommitState, + onAbortState, + feeCalculator, + nil, + nil, + nil, + ) } func (v *verifier) ApricotStandardBlock(b *block.ApricotStandardBlock) error { @@ -183,7 +199,12 @@ func (v *verifier) ApricotStandardBlock(b *block.ApricotStandardBlock) error { timestamp = onAcceptState.GetTimestamp() // Equal to parent timestamp feeCalculator = state.NewStaticFeeCalculator(v.txExecutorBackend.Config, timestamp) ) - return v.standardBlock(b, feeCalculator, onAcceptState) + return v.standardBlock( + b, + b.Transactions, + feeCalculator, + onAcceptState, + ) } func (v *verifier) ApricotAtomicBlock(b *block.ApricotAtomicBlock) error { @@ -360,7 +381,8 @@ func (v *verifier) commitBlock(b block.Block) error { // proposalBlock populates the state of this block if [nil] is returned func (v *verifier) proposalBlock( - b *block.ApricotProposalBlock, + b block.Block, + tx *txs.Tx, onDecisionState state.Diff, onCommitState state.Diff, onAbortState state.Diff, @@ -374,19 +396,19 @@ func (v *verifier) proposalBlock( OnAbortState: onAbortState, Backend: v.txExecutorBackend, FeeCalculator: feeCalculator, - Tx: b.Tx, + Tx: tx, } - if err := b.Tx.Unsigned.Visit(&txExecutor); err != nil { - txID := b.Tx.ID() + if err := tx.Unsigned.Visit(&txExecutor); err != nil { + txID := tx.ID() v.MarkDropped(txID, err) // cache tx as dropped return err } - onCommitState.AddTx(b.Tx, status.Committed) - onAbortState.AddTx(b.Tx, status.Aborted) + onCommitState.AddTx(tx, status.Committed) + onAbortState.AddTx(tx, status.Aborted) - v.Mempool.Remove(b.Tx) + v.Mempool.Remove(tx) blkID := b.ID() v.blkIDToState[blkID] = &blockState{ @@ -413,16 +435,22 @@ func (v *verifier) proposalBlock( // standardBlock populates the state of this block if [nil] is returned func (v *verifier) standardBlock( - b *block.ApricotStandardBlock, + b block.Block, + txs []*txs.Tx, feeCalculator fee.Calculator, onAcceptState state.Diff, ) error { - inputs, atomicRequests, onAcceptFunc, err := v.processStandardTxs(b.Transactions, feeCalculator, onAcceptState, b.Parent()) + inputs, atomicRequests, onAcceptFunc, err := v.processStandardTxs( + txs, + feeCalculator, + onAcceptState, + b.Parent(), + ) if err != nil { return err } - v.Mempool.Remove(b.Transactions...) + v.Mempool.Remove(txs...) blkID := b.ID() v.blkIDToState[blkID] = &blockState{ diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 5b786b0e33d8..f57b8fb4ed58 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -1184,8 +1184,9 @@ func TestBlockExecutionWithComplexity(t *testing.T) { } require.Contains(verifier.blkIDToState, blkID) - onAcceptState := verifier.blkIDToState[blkID].onAcceptState - require.Equal(test.expectedFeeState, onAcceptState.GetFeeState()) + blockState := verifier.blkIDToState[blkID] + require.Equal(blk, blockState.statelessBlock) + require.Equal(test.expectedFeeState, blockState.onAcceptState.GetFeeState()) }) } } From 03fc83b07522169869d8372da404669570af09d6 Mon Sep 17 00:00:00 2001 From: Meaghan FitzGerald Date: Thu, 10 Oct 2024 12:58:25 -0400 Subject: [PATCH 199/199] update uri Signed-off-by: Meaghan FitzGerald --- wallet/subnet/primary/examples/convert-subnet/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/subnet/primary/examples/convert-subnet/main.go b/wallet/subnet/primary/examples/convert-subnet/main.go index c306aca3549d..d62b4e459c25 100644 --- a/wallet/subnet/primary/examples/convert-subnet/main.go +++ b/wallet/subnet/primary/examples/convert-subnet/main.go @@ -21,7 +21,7 @@ import ( func main() { key := genesis.EWOQKey - uri := "http://localhost:9700" + uri := primary.LocalAPIURI kc := secp256k1fx.NewKeychain(key) subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") chainID := ids.FromStringOrPanic("4R1dLAnG45P3rbdJB2dWuKdVRZF3dLMKgfJ8J6wKSQvYFVUhb")