From 9c8835bd790732c61d0da189f65ea7f2fc50f808 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 24 Jul 2024 16:42:09 +0300 Subject: [PATCH 001/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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/400] 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 8fe987cf89f72411161ad06feffd1740016b98d2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 30 Sep 2024 19:20:51 -0400 Subject: [PATCH 085/400] 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 086/400] 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 087/400] 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 088/400] 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 089/400] 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 090/400] 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 091/400] 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 092/400] 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 093/400] 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 094/400] 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 095/400] 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 096/400] 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 097/400] 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 098/400] 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 099/400] 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 100/400] 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 101/400] 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 102/400] 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 103/400] 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 104/400] 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 105/400] 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 106/400] 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 107/400] 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 108/400] 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 fa12221e3179559e385f5549fc9373f40f1cbbc4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 12:44:38 -0400 Subject: [PATCH 109/400] 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 891bca4232d3690c64248e376e1efd961fba94bc Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 15:25:01 -0400 Subject: [PATCH 110/400] 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 111/400] 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 112/400] 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 113/400] 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 114/400] 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 115/400] 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 116/400] 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 117/400] 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 646ce06f5be18e2c990bf370362af831fae581a4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 2 Oct 2024 18:39:41 -0400 Subject: [PATCH 118/400] 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 119/400] 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 120/400] 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 121/400] 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 01c9072aafc84e22efeb0ba0ff7f7da9d8b280cd Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 3 Oct 2024 16:17:00 -0400 Subject: [PATCH 122/400] 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 123/400] 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 124/400] 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 125/400] 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 126/400] 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 127/400] 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 128/400] 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 129/400] 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 130/400] 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 131/400] 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 132/400] 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 133/400] 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 3178ec0f830a791d52b0415d68f62c0f56b81a5d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 15:07:05 -0400 Subject: [PATCH 134/400] 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 135/400] 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 795243c26568e4819c4b183244dc150cab1043ba Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 15:27:15 -0400 Subject: [PATCH 136/400] 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 137/400] 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 9ef28a194141206de144a9be4420fc0149799521 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 4 Oct 2024 15:49:38 -0400 Subject: [PATCH 138/400] 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 139/400] 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 140/400] 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 141/400] 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 142/400] 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 143/400] 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 144/400] 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 145/400] 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 146/400] 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 147/400] 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 148/400] 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 149/400] 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 150/400] 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 151/400] 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 152/400] 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 153/400] 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 154/400] 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 8b32e95f942e183aeff72d635fa26399a01568b8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 13:03:07 -0400 Subject: [PATCH 155/400] 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 156/400] 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 157/400] 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 158/400] 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 159/400] 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 160/400] 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 161/400] 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 162/400] 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 163/400] 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 164/400] 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 165/400] 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 166/400] 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 167/400] 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 168/400] 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 169/400] 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 170/400] 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 171/400] 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 172/400] 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 173/400] 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 174/400] 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 175/400] 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 176/400] 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 177/400] 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 178/400] 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 179/400] 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 180/400] 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 181/400] 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 2b407e9139786e6fb40872170fb6d4c28dfdd0f7 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 8 Oct 2024 19:22:46 -0400 Subject: [PATCH 182/400] 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 183/400] 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 184/400] 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 8d7adfc5b0ebdced9492e290b4cd3574d34fd222 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 11 Oct 2024 15:13:39 -0400 Subject: [PATCH 185/400] Remove unused testing code --- tests/e2e/p/permissionless_layer_one.go | 26 ------------------------- 1 file changed, 26 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 4efbe74800b6..4dfb5633b8f7 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -4,7 +4,6 @@ package p import ( - "context" "math" "time" @@ -14,11 +13,8 @@ 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/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" @@ -27,7 +23,6 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/txs" "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" @@ -140,27 +135,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { genesisNodePK, err := bls.PublicKeyFromCompressedBytes(genesisNodePoP.PublicKey[:]) require.NoError(err) - tc.By("connecting to the genesis validator") - var ( - networkID = env.GetNetwork().GetNetworkID() - genesisPeerMessages = buffer.NewUnboundedBlockingDeque[p2pmessage.InboundMessage](1) - ) - 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) - defer func() { - genesisPeerMessages.Close() - genesisPeer.StartClose() - require.NoError(genesisPeer.AwaitClosed(tc.DefaultContext())) - }() - address := []byte{} tc.By("issuing a ConvertSubnetTx", func() { _, err := pWallet.IssueConvertSubnetTx( From 915de36538a8dde567a46bf7d1f27608e7b7b310 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 13 Oct 2024 23:17:05 -0400 Subject: [PATCH 186/400] Fix merge --- .../txs/executor/standard_tx_executor.go | 2 +- .../txs/executor/standard_tx_executor_test.go | 29 +++++++++++-------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 098051c0dab6..828917435f83 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -569,7 +569,7 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { } if vdr.Balance != 0 { // We are attempting to add an active validator - if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeCapacity { + if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeConfig.Capacity { return errMaxNumActiveValidators } diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index afdd3ec14940..f5eaf19a0a25 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -37,13 +37,15 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/state/statetest" "github.com/ava-labs/avalanchego/vms/platformvm/status" "github.com/ava-labs/avalanchego/vms/platformvm/txs" - "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" "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" + + txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) // This tests that the math performed during TransformSubnetTx execution can @@ -2384,10 +2386,9 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { var ( ctx = snowtest.Context(t, constants.PlatformChainID) defaultConfig = &config.Config{ - DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, - ValidatorFeeCapacity: genesis.LocalParams.ValidatorFeeCapacity, - ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, - UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), + DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, + ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, + UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), } baseState = statetest.New(t, statetest.Config{ Upgrades: defaultConfig.UpgradeConfig, @@ -2501,19 +2502,23 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { { name: "invalid fee calculation", updateExecutor: func(e *StandardTxExecutor) error { - e.FeeCalculator = fee.NewStaticCalculator(e.Config.StaticFeeConfig) + e.FeeCalculator = txfee.NewStaticCalculator(e.Config.StaticFeeConfig) return nil }, - expectedErr: fee.ErrUnsupportedTx, + expectedErr: txfee.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), + DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, + ValidatorFeeConfig: validatorfee.Config{ + Capacity: 0, + Target: genesis.LocalParams.ValidatorFeeConfig.Target, + MinPrice: genesis.LocalParams.ValidatorFeeConfig.MinPrice, + ExcessConversionConstant: genesis.LocalParams.ValidatorFeeConfig.ExcessConversionConstant, + }, + UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), } return nil }, @@ -2534,7 +2539,7 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { { name: "insufficient fee", updateExecutor: func(e *StandardTxExecutor) error { - e.FeeCalculator = fee.NewDynamicCalculator( + e.FeeCalculator = txfee.NewDynamicCalculator( e.Config.DynamicFeeConfig.Weights, 100*genesis.LocalParams.DynamicFeeConfig.MinPrice, ) From 96625ae9c5c7b1fc5df67e785fc4e7c554fe5ad3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 13 Oct 2024 23:27:41 -0400 Subject: [PATCH 187/400] Fix merge --- 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 4068dca732ff..64ab3e5db8cf 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -772,7 +772,7 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal } if tx.Balance != 0 { // We are attempting to add an active validator - if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeCapacity { + if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeConfig.Capacity { return errMaxNumActiveValidators } From 702639ae1ccb19ad3dbefd88e50dadfb2a2f36cf Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 19 Oct 2024 13:38:28 -0400 Subject: [PATCH 188/400] ACP-77: Add ConversionID to state --- vms/platformvm/client.go | 4 +- vms/platformvm/config/execution_config.go | 4 +- .../config/execution_config_test.go | 2 +- vms/platformvm/service.go | 6 +- vms/platformvm/state/diff.go | 43 +++++----- vms/platformvm/state/diff_test.go | 68 ++++++++++----- 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 | 86 ++++++++++--------- vms/platformvm/state/state_test.go | 60 +++++++------ .../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 +- 16 files changed, 235 insertions(+), 180 deletions(-) diff --git a/vms/platformvm/client.go b/vms/platformvm/client.go index 54554f37d92b..522d359055d8 100644 --- a/vms/platformvm/client.go +++ b/vms/platformvm/client.go @@ -235,7 +235,8 @@ type GetSubnetClientResponse struct { Locktime uint64 // subnet transformation tx ID for a permissionless subnet SubnetTransformationTxID ids.ID - // subnet manager information for a permissionless L1 + // subnet conversion 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 diff --git a/vms/platformvm/config/execution_config.go b/vms/platformvm/config/execution_config.go index fafdaf9b99d2..e5bef1637d05 100644 --- a/vms/platformvm/config/execution_config.go +++ b/vms/platformvm/config/execution_config.go @@ -21,7 +21,7 @@ var DefaultExecutionConfig = ExecutionConfig{ ChainDBCacheSize: 2048, BlockIDCacheSize: 8192, FxOwnerCacheSize: 4 * units.MiB, - SubnetManagerCacheSize: 4 * units.MiB, + SubnetConversionCacheSize: 4 * units.MiB, ChecksumsEnabled: false, MempoolPruneFrequency: 30 * time.Minute, } @@ -37,7 +37,7 @@ type ExecutionConfig struct { 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"` + SubnetConversionCacheSize int `json:"subnet-conversion-cache-size"` ChecksumsEnabled bool `json:"checksums-enabled"` MempoolPruneFrequency time.Duration `json:"mempool-prune-frequency"` } diff --git a/vms/platformvm/config/execution_config_test.go b/vms/platformvm/config/execution_config_test.go index 5929a75f9063..c938c177add3 100644 --- a/vms/platformvm/config/execution_config_test.go +++ b/vms/platformvm/config/execution_config_test.go @@ -89,7 +89,7 @@ func TestExecutionConfigUnmarshal(t *testing.T) { ChainDBCacheSize: 7, BlockIDCacheSize: 8, FxOwnerCacheSize: 9, - SubnetManagerCacheSize: 10, + SubnetConversionCacheSize: 10, ChecksumsEnabled: true, MempoolPruneFrequency: time.Minute, } diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 91b7810d30df..b7111318c02a 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -439,7 +439,8 @@ type GetSubnetResponse struct { Locktime avajson.Uint64 `json:"locktime"` // subnet transformation tx ID for an elastic subnet SubnetTransformationTxID ids.ID `json:"subnetTransformationTxID"` - // subnet manager information for a permissionless L1 + // subnet conversion information for a permissionless L1 + ConversionID ids.ID `json:"conversionID"` ManagerChainID ids.ID `json:"managerChainID"` ManagerAddress types.JSONByteSlice `json:"managerAddress"` } @@ -490,9 +491,10 @@ 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: diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 24bdabfa96da..579b10bb28db 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -52,8 +52,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 @@ -76,14 +76,14 @@ 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(), - 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(), + expiryDiff: newExpiryDiff(), + subnetOwners: make(map[ids.ID]fx.Owner), + subnetConversions: make(map[ids.ID]subnetConversion), }, nil } @@ -357,23 +357,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 c, ok := d.subnetConversions[subnetID]; ok { + return c.ConversionID, c.ChainID, c.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, } } @@ -576,8 +577,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, c := range d.subnetConversions { + baseState.SetSubnetConversion(subnetID, c.ConversionID, c.ChainID, c.Addr) } return nil } diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index 3b091cd67a69..408ac9fe1cbf 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -774,44 +774,68 @@ 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()) + expectedConversion = subnetConversion{ + ConversionID: ids.GenerateTestID(), + ChainID: ids.GenerateTestID(), + Addr: []byte{1, 2, 3, 4}, + } + subnetID = ids.GenerateTestID() ) - 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(subnetConversion{ + ConversionID: conversionID, + ChainID: chainID, + Addr: addr, + }) d, err := NewDiffOn(state) require.NoError(err) - chainID, addr, err = d.GetSubnetManager(subnetID) + conversionID, chainID, addr, err = d.GetSubnetConversion(subnetID) require.ErrorIs(err, database.ErrNotFound) - require.Equal(ids.Empty, chainID) - require.Nil(addr) + require.Zero(subnetConversion{ + ConversionID: conversionID, + ChainID: chainID, + Addr: 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) + // Setting a subnet conversion should be reflected on diff not state + 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(subnetConversion{ + ConversionID: conversionID, + ChainID: chainID, + Addr: 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 3b380a87a8b8..f4f8dc661b90 100644 --- a/vms/platformvm/state/mock_chain.go +++ b/vms/platformvm/state/mock_chain.go @@ -353,20 +353,21 @@ func (mr *MockChainMockRecorder) GetPendingValidator(subnetID, nodeID any) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingValidator", reflect.TypeOf((*MockChain)(nil).GetPendingValidator), subnetID, nodeID) } -// 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) } // GetSubnetOwner mocks base method. @@ -573,16 +574,16 @@ func (mr *MockChainMockRecorder) SetFeeState(f any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFeeState", reflect.TypeOf((*MockChain)(nil).SetFeeState), f) } -// 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 77edfde92aaf..bbeed71080b1 100644 --- a/vms/platformvm/state/mock_diff.go +++ b/vms/platformvm/state/mock_diff.go @@ -367,20 +367,21 @@ func (mr *MockDiffMockRecorder) GetPendingValidator(subnetID, nodeID any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingValidator", reflect.TypeOf((*MockDiff)(nil).GetPendingValidator), subnetID, nodeID) } -// 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) } // GetSubnetOwner mocks base method. @@ -587,16 +588,16 @@ func (mr *MockDiffMockRecorder) SetFeeState(f any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFeeState", reflect.TypeOf((*MockDiff)(nil).SetFeeState), f) } -// 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 f602345688b1..ed44205d9b49 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -557,6 +557,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() @@ -572,22 +589,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) -} - // GetSubnetOwner mocks base method. func (m *MockState) GetSubnetOwner(subnetID ids.ID) (fx.Owner, error) { m.ctrl.T.Helper() @@ -846,16 +847,16 @@ func (mr *MockStateMockRecorder) SetLastAccepted(blkID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLastAccepted", reflect.TypeOf((*MockState)(nil).SetLastAccepted), blkID) } -// 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 f351bf5940b2..c839d4b7034b 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -78,7 +78,7 @@ var ( UTXOPrefix = []byte("utxo") SubnetPrefix = []byte("subnet") SubnetOwnerPrefix = []byte("subnetOwner") - SubnetManagerPrefix = []byte("subnetManager") + SubnetConversionPrefix = []byte("subnetConversion") TransformedSubnetPrefix = []byte("transformedSubnet") SupplyPrefix = []byte("supply") ChainPrefix = []byte("chain") @@ -123,8 +123,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) (conversionID ids.ID, chainID ids.ID, addr []byte, err error) + SetSubnetConversion(subnetID ids.ID, conversionID ids.ID, chainID ids.ID, addr []byte) GetSubnetTransformation(subnetID ids.ID) (*txs.Tx, error) AddSubnetTransformation(transformSubnetTx *txs.Tx) @@ -275,7 +275,9 @@ type stateBlk struct { * | '-. list * | '-- txID -> nil * |-. subnetOwners - * | '-. subnetID -> owner + * | '-- subnetID -> owner + * |-. subnetConversions + * | '-- subnetID -> conversionID + chainID + addr * |-. chains * | '-. subnetID * | '-. list @@ -364,9 +366,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 -> conversion of the subnet + subnetConversionCache cache.Cacher[ids.ID, subnetConversion] // cache of subnetID -> conversion + 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 @@ -439,9 +441,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 { @@ -552,12 +555,12 @@ func New( return nil, err } - subnetManagerDB := prefixdb.New(SubnetManagerPrefix, baseDB) - subnetManagerCache, err := metercacher.New[ids.ID, chainIDAndAddr]( + subnetConversionDB := prefixdb.New(SubnetConversionPrefix, baseDB) + subnetConversionCache, err := metercacher.New[ids.ID, subnetConversion]( "subnet_manager_cache", metricsReg, - cache.NewSizedLRU[ids.ID, chainIDAndAddr](execCfg.SubnetManagerCacheSize, func(_ ids.ID, f chainIDAndAddr) int { - return 2*ids.IDLen + len(f.Addr) + cache.NewSizedLRU[ids.ID, subnetConversion](execCfg.SubnetConversionCacheSize, func(_ ids.ID, c subnetConversion) int { + return 3*ids.IDLen + len(c.Addr) }), ) if err != nil { @@ -666,9 +669,9 @@ func New( subnetOwnerDB: subnetOwnerDB, subnetOwnerCache: subnetOwnerCache, - subnetManagers: make(map[ids.ID]chainIDAndAddr), - subnetManagerDB: subnetManagerDB, - subnetManagerCache: subnetManagerCache, + subnetConversions: make(map[ids.ID]subnetConversion), + subnetConversionDB: subnetConversionDB, + subnetConversionCache: subnetConversionCache, transformedSubnets: make(map[ids.ID]*txs.Tx), transformedSubnetCache: transformedSubnetCache, @@ -856,32 +859,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 c, ok := s.subnetConversions[subnetID]; ok { + return c.ConversionID, c.ChainID, c.Addr, nil } - if chainIDAndAddr, cached := s.subnetManagerCache.Get(subnetID); cached { - return chainIDAndAddr.ChainID, chainIDAndAddr.Addr, nil + if c, ok := s.subnetConversionCache.Get(subnetID); ok { + return c.ConversionID, c.ChainID, c.Addr, nil } - chainIDAndAddrBytes, err := s.subnetManagerDB.Get(subnetID[:]) + bytes, 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 c subnetConversion + if _, err := block.GenesisCodec.Unmarshal(bytes, &c); err != nil { + return ids.Empty, ids.Empty, nil, err } - s.subnetManagerCache.Put(subnetID, manager) - return manager.ChainID, manager.Addr, nil + s.subnetConversionCache.Put(subnetID, c) + return c.ConversionID, c.ChainID, c.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, } } @@ -1765,7 +1769,7 @@ func (s *state) write(updateValidators bool, height uint64) error { s.writeUTXOs(), s.writeSubnets(), s.writeSubnetOwners(), - s.writeSubnetManagers(), + s.writeSubnetConversions(), s.writeTransformedSubnets(), s.writeSubnetSupplies(), s.writeChains(), @@ -2364,20 +2368,20 @@ func (s *state) writeSubnetOwners() error { return nil } -func (s *state) writeSubnetManagers() error { - for subnetID, manager := range s.subnetManagers { +func (s *state) writeSubnetConversions() error { + for subnetID, c := range s.subnetConversions { subnetID := subnetID - manager := manager - delete(s.subnetManagers, subnetID) + c := c + delete(s.subnetConversions, subnetID) - managerBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &manager) + bytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &c) if err != nil { return fmt.Errorf("failed to marshal subnet manager: %w", err) } - s.subnetManagerCache.Put(subnetID, manager) + s.subnetConversionCache.Put(subnetID, c) - if err := s.subnetManagerDB.Put(subnetID[:], managerBytes); err != nil { + if err := s.subnetConversionDB.Put(subnetID[:], bytes); 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 0f23a3ebc833..ea13a2dd30ac 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1323,52 +1323,58 @@ func TestStateSubnetOwner(t *testing.T) { require.Equal(owner2, owner) } -func TestStateSubnetManager(t *testing.T) { +func TestStateSubnetConversion(t *testing.T) { tests := []struct { name string - setup func(t *testing.T, s State, subnetID ids.ID, chainID ids.ID, addr []byte) + setup func(s *state, subnetID ids.ID, c subnetConversion) }{ { name: "in-memory", - setup: func(_ *testing.T, s State, subnetID ids.ID, chainID ids.ID, addr []byte) { - s.SetSubnetManager(subnetID, chainID, addr) + setup: func(s *state, subnetID ids.ID, c subnetConversion) { + s.SetSubnetConversion(subnetID, c.ConversionID, c.ChainID, c.Addr) }, }, { name: "cache", - setup: func(t *testing.T, s State, subnetID ids.ID, chainID ids.ID, addr []byte) { - subnetManagerCache := s.(*state).subnetManagerCache - - require.Zero(t, subnetManagerCache.Len()) - subnetManagerCache.Put(subnetID, chainIDAndAddr{ - ChainID: chainID, - Addr: addr, - }) - require.Equal(t, 1, subnetManagerCache.Len()) + setup: func(s *state, subnetID ids.ID, c subnetConversion) { + s.subnetConversionCache.Flush() + s.subnetConversionCache.Put(subnetID, c) }, }, } 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) + initializedState = 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 := initializedState.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(subnetConversion{ + ConversionID: conversionID, + ChainID: chainID, + Addr: addr, + }) - test.setup(t, initializedState, subnetID, expectedChainID, expectedAddr) + test.setup(initializedState, subnetID, expectedConversion) - chainID, addr, err = initializedState.GetSubnetManager(subnetID) + conversionID, chainID, addr, err = initializedState.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..837af0ba73a5 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) + stateConversionID, stateChainID, stateAddress, err := diff.GetSubnetConversion(subnetID) require.NoError(err) + // TODO: Update this test when we populate the correct conversionID + 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) } From 4b562f05e10ecd9c881f01db8799bb82ec92e1c0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 19 Oct 2024 13:45:02 -0400 Subject: [PATCH 189/400] nit --- vms/platformvm/state/diff_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index 408ac9fe1cbf..3bb156c5096b 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -772,7 +772,7 @@ func TestDiffSubnetOwner(t *testing.T) { require.Equal(owner2, owner) } -func TestDiffSubnetManager(t *testing.T) { +func TestDiffSubnetConversion(t *testing.T) { var ( require = require.New(t) state = newTestState(t, memdb.New()) From 7f3dd5e39afe01672de659518671e36f1c2039bf Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 19 Oct 2024 13:49:13 -0400 Subject: [PATCH 190/400] nit --- vms/platformvm/state/diff.go | 2 +- vms/platformvm/state/diff_test.go | 2 +- vms/platformvm/state/state.go | 4 ++-- vms/platformvm/txs/executor/standard_tx_executor.go | 2 +- vms/platformvm/txs/executor/standard_tx_executor_test.go | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 579b10bb28db..e2cfd648296d 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -362,7 +362,7 @@ func (d *diff) GetSubnetConversion(subnetID ids.ID) (ids.ID, ids.ID, []byte, err return c.ConversionID, c.ChainID, c.Addr, nil } - // If the subnet manager was not assigned in this diff, ask the parent state. + // If the subnet conversion was not assigned in this diff, ask the parent state. parentState, ok := d.stateVersions.GetState(d.parentID) if !ok { return ids.Empty, ids.Empty, nil, ErrMissingParentState diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index 3bb156c5096b..c62333a383a9 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -824,7 +824,7 @@ func TestDiffSubnetConversion(t *testing.T) { Addr: addr, }) - // State should reflect new subnet manager after diff is applied + // State should reflect new subnet conversion after diff is applied require.NoError(d.Apply(state)) conversionID, chainID, addr, err = state.GetSubnetConversion(subnetID) require.NoError(err) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index c839d4b7034b..d626639bf0f7 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2376,13 +2376,13 @@ func (s *state) writeSubnetConversions() error { bytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &c) if err != nil { - return fmt.Errorf("failed to marshal subnet manager: %w", err) + return fmt.Errorf("failed to marshal subnet conversion: %w", err) } s.subnetConversionCache.Put(subnetID, c) if err := s.subnetConversionDB.Put(subnetID[:], bytes); err != nil { - return fmt.Errorf("failed to write subnet manager: %w", err) + return fmt.Errorf("failed to write subnet conversion: %w", err) } } return nil diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 14069b1e4e43..9cc9eddf723b 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -541,7 +541,7 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { avax.Consume(e.State, tx.Ins) // Produce the UTXOS avax.Produce(e.State, txID, tx.Outs) - // Set the new Subnet manager in the database + // Track the subnet conversion in the database // 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 837af0ba73a5..64686a1c744e 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -1996,7 +1996,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { expectedErr: ErrFlowCheckFailed, }, { - name: "attempted to remove subnet validator after subnet manager is set", + name: "attempted to remove subnet validator after subnet conversion has occurred", newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) env.state.EXPECT().GetSubnetConversion(env.unsignedTx.Subnet).Return(ids.GenerateTestID(), ids.GenerateTestID(), []byte{'a', 'd', 'd', 'r', 'e', 's', 's'}, nil).AnyTimes() @@ -2281,7 +2281,7 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { err: ErrFlowCheckFailed, }, { - name: "invalid if subnet manager is set", + name: "invalid after subnet conversion", newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) { env := newValidTransformSubnetTxVerifyEnv(t, ctrl) From d64794b61b7d17e6320e1531a5e1b40cc13afad4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 19 Oct 2024 14:08:41 -0400 Subject: [PATCH 191/400] merged --- tests/e2e/p/permissionless_layer_one.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 8922a11423a2..8dfad5a9f21d 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -358,7 +358,6 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) }) -<<<<<<< HEAD var nextNonce uint64 setWeight := func(validationID ids.ID, weight uint64) { tc.By("creating the unsigned warp message") @@ -452,9 +451,8 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) }) }) -======= + _ = e2e.CheckBootstrapIsPossible(tc, env.GetNetwork()) ->>>>>>> implement-acp-77-register-subnet-validator-tx }) }) From f40c1b3ff8bc3877ec4e5edde410ec62b6fc27b0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 19 Oct 2024 14:15:41 -0400 Subject: [PATCH 192/400] nit --- vms/platformvm/state/diff_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index c62333a383a9..7441786ac1bf 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -776,12 +776,12 @@ func TestDiffSubnetConversion(t *testing.T) { var ( 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}, } - subnetID = ids.GenerateTestID() ) conversionID, chainID, addr, err := state.GetSubnetConversion(subnetID) From 51f009b9e972be459794aadba2fd67a0ad3417f8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 19 Oct 2024 14:23:53 -0400 Subject: [PATCH 193/400] nit --- vms/platformvm/state/state.go | 2 +- vms/platformvm/state/state_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index d626639bf0f7..04ed6b619b4d 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -557,7 +557,7 @@ func New( subnetConversionDB := prefixdb.New(SubnetConversionPrefix, baseDB) subnetConversionCache, err := metercacher.New[ids.ID, subnetConversion]( - "subnet_manager_cache", + "subnet_conversion_cache", metricsReg, cache.NewSizedLRU[ids.ID, subnetConversion](execCfg.SubnetConversionCacheSize, func(_ ids.ID, c subnetConversion) int { return 3*ids.IDLen + len(c.Addr) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index ea13a2dd30ac..f2e0483a25c6 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1346,7 +1346,7 @@ func TestStateSubnetConversion(t *testing.T) { t.Run(test.name, func(t *testing.T) { var ( require = require.New(t) - initializedState = newTestState(t, memdb.New()) + state = newTestState(t, memdb.New()) subnetID = ids.GenerateTestID() expectedConversion = subnetConversion{ ConversionID: ids.GenerateTestID(), @@ -1355,7 +1355,7 @@ func TestStateSubnetConversion(t *testing.T) { } ) - conversionID, chainID, addr, err := initializedState.GetSubnetConversion(subnetID) + conversionID, chainID, addr, err := state.GetSubnetConversion(subnetID) require.ErrorIs(err, database.ErrNotFound) require.Zero(subnetConversion{ ConversionID: conversionID, @@ -1363,9 +1363,9 @@ func TestStateSubnetConversion(t *testing.T) { Addr: addr, }) - test.setup(initializedState, subnetID, expectedConversion) + test.setup(state, subnetID, expectedConversion) - conversionID, chainID, addr, err = initializedState.GetSubnetConversion(subnetID) + conversionID, chainID, addr, err = state.GetSubnetConversion(subnetID) require.NoError(err) require.Equal( expectedConversion, From 05a7b136901e4dfed52d1eed9e6cf53be00ad391 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 19 Oct 2024 14:26:51 -0400 Subject: [PATCH 194/400] nit --- vms/platformvm/service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index b7111318c02a..7c3bbc4596be 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -498,6 +498,7 @@ func (s *Service) GetSubnet(_ *http.Request, args *GetSubnetArgs, response *GetS response.ManagerChainID = chainID response.ManagerAddress = addr case database.ErrNotFound: + response.ConversionID = ids.Empty response.ManagerChainID = ids.Empty response.ManagerAddress = []byte(nil) default: From a1db89e32a8265019b13bc5207e6d9b1a859b4f7 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 19 Oct 2024 14:31:26 -0400 Subject: [PATCH 195/400] nit --- vms/platformvm/state/state.go | 1 + 1 file changed, 1 insertion(+) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 04ed6b619b4d..0c92fde71ec5 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1795,6 +1795,7 @@ func (s *state) Close() error { s.rewardUTXODB.Close(), s.utxoDB.Close(), s.subnetBaseDB.Close(), + s.subnetConversionDB.Close(), s.transformedSubnetDB.Close(), s.supplyDB.Close(), s.chainDB.Close(), From 575b36ac37b279b5cdef3fdf0d31e4f8c3cc23fb Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 19 Oct 2024 16:19:38 -0400 Subject: [PATCH 196/400] remove unneeded assignment --- vms/platformvm/state/state.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 0c92fde71ec5..a059323f6874 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2371,8 +2371,6 @@ func (s *state) writeSubnetOwners() error { func (s *state) writeSubnetConversions() error { for subnetID, c := range s.subnetConversions { - subnetID := subnetID - c := c delete(s.subnetConversions, subnetID) bytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &c) From d2d0f69976f54fd3827c840260fb3d9ec667345d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 19 Oct 2024 16:26:46 -0400 Subject: [PATCH 197/400] update comment --- vms/platformvm/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/client.go b/vms/platformvm/client.go index 522d359055d8..505c545bdded 100644 --- a/vms/platformvm/client.go +++ b/vms/platformvm/client.go @@ -235,7 +235,7 @@ type GetSubnetClientResponse struct { Locktime uint64 // subnet transformation tx ID for a permissionless subnet SubnetTransformationTxID ids.ID - // subnet conversion information for a permissionless L1 + // subnet conversion information for an L1 ConversionID ids.ID ManagerChainID ids.ID ManagerAddress []byte From b6b6515ffa8458fc5df186f990bbeca3a64b5caa Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 19 Oct 2024 16:51:41 -0400 Subject: [PATCH 198/400] update comment --- vms/platformvm/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 7c3bbc4596be..72be28e40f4d 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -439,7 +439,7 @@ type GetSubnetResponse struct { Locktime avajson.Uint64 `json:"locktime"` // subnet transformation tx ID for an elastic subnet SubnetTransformationTxID ids.ID `json:"subnetTransformationTxID"` - // subnet conversion information for a permissionless L1 + // subnet conversion information for an L1 ConversionID ids.ID `json:"conversionID"` ManagerChainID ids.ID `json:"managerChainID"` ManagerAddress types.JSONByteSlice `json:"managerAddress"` From 6ffcd1b30910226ef2dc8bf9c133598eb4bc49d6 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 19 Oct 2024 17:27:33 -0400 Subject: [PATCH 199/400] update doc --- vms/platformvm/service.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/service.md b/vms/platformvm/service.md index f4cd28cd35f1..81c929b0ecda 100644 --- a/vms/platformvm/service.md +++ b/vms/platformvm/service.md @@ -1204,7 +1204,7 @@ Testnet: U8iRqJoiJm8xZHAacmvYyZVwqQx6uDNtQeP3CQ6fcgQk3JqnK ### `platform.getSubnet` -Get owners and elastic info about the Subnet. +Get owners and elastic info about the Subnet or L1. **Signature:** @@ -1217,7 +1217,10 @@ platform.getSubnet({ controlKeys: []string, threshold: string, locktime: string, - subnetTransformationTxID: string + subnetTransformationTxID: string, + conversionID: string, + managerChainID: string, + managerAddress: string } ``` @@ -1226,8 +1229,10 @@ platform.getSubnet({ a permissioned subnet. If the Subnet is a PoS Subnet, then `threshold` will be `0` and `controlKeys` will be empty. - changes can not be made into the subnet until `locktime` is in the past. -- `subnetTransformationTxID` is the ID of the transaction that changed the subnet into a elastic one, - for when this change was performed. +- `subnetTransformationTxID` is the ID of the transaction that changed the subnet into an elastic one, if it exists. +- `conversionID` is the ID of the conversion from a permissioned Subnet into an L1, if it exists. +- `managerChainID` is the ChainID that has the ability to modify this L1s validator set, if it exists. +- `managerAddress` is the address that has the ability to modify this L1s validator set, if it exists. **Example Call:** @@ -1250,7 +1255,10 @@ curl -X POST --data '{ "controlKeys": ["P-fuji1ztvstx6naeg6aarfd047fzppdt8v4gsah88e0c","P-fuji193kvt4grqewv6ce2x59wnhydr88xwdgfcedyr3"], "threshold": "1", "locktime": "0", - "subnetTransformationTxID": "11111111111111111111111111111111LpoYY" + "subnetTransformationTxID": "11111111111111111111111111111111LpoYY", + "conversionID": "11111111111111111111111111111111LpoYY", + "managerChainID": "11111111111111111111111111111111LpoYY", + "managerAddress": null }, "id": 1 } From ad001806ca9f191294126842e766b8043450b5bb Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 20 Oct 2024 13:15:23 -0400 Subject: [PATCH 200/400] Address comments --- vms/platformvm/service.go | 8 +-- vms/platformvm/service.md | 2 +- vms/platformvm/state/diff.go | 20 +++----- vms/platformvm/state/diff_test.go | 50 +++++-------------- vms/platformvm/state/mock_chain.go | 18 +++---- vms/platformvm/state/mock_diff.go | 18 +++---- vms/platformvm/state/mock_state.go | 18 +++---- vms/platformvm/state/state.go | 38 +++++++------- vms/platformvm/state/state_test.go | 30 ++++------- .../txs/executor/staker_tx_verification.go | 2 +- .../txs/executor/standard_tx_executor.go | 11 +++- .../txs/executor/subnet_tx_verification.go | 2 +- 12 files changed, 86 insertions(+), 131 deletions(-) diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 72be28e40f4d..22153a97ece6 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -491,12 +491,12 @@ func (s *Service) GetSubnet(_ *http.Request, args *GetSubnetArgs, response *GetS return err } - switch conversionID, chainID, addr, err := s.vm.state.GetSubnetConversion(args.SubnetID); err { + switch c, err := s.vm.state.GetSubnetConversion(args.SubnetID); err { case nil: response.IsPermissioned = false - response.ConversionID = conversionID - response.ManagerChainID = chainID - response.ManagerAddress = addr + response.ConversionID = c.ConversionID + response.ManagerChainID = c.ChainID + response.ManagerAddress = c.Addr case database.ErrNotFound: response.ConversionID = ids.Empty response.ManagerChainID = ids.Empty diff --git a/vms/platformvm/service.md b/vms/platformvm/service.md index 81c929b0ecda..dbb12907694b 100644 --- a/vms/platformvm/service.md +++ b/vms/platformvm/service.md @@ -1204,7 +1204,7 @@ Testnet: U8iRqJoiJm8xZHAacmvYyZVwqQx6uDNtQeP3CQ6fcgQk3JqnK ### `platform.getSubnet` -Get owners and elastic info about the Subnet or L1. +Get owners and info about the Subnet or L1. **Signature:** diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index e2cfd648296d..9fe6a62363c0 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -53,7 +53,7 @@ type diff struct { // Subnet ID --> Owner of the subnet subnetOwners map[ids.ID]fx.Owner // Subnet ID --> Conversion of the subnet - subnetConversions map[ids.ID]subnetConversion + subnetConversions map[ids.ID]SubnetConversion // Subnet ID --> Tx that transforms the subnet transformedSubnets map[ids.ID]*txs.Tx @@ -83,7 +83,7 @@ func NewDiff( accruedFees: parentState.GetAccruedFees(), expiryDiff: newExpiryDiff(), subnetOwners: make(map[ids.ID]fx.Owner), - subnetConversions: make(map[ids.ID]subnetConversion), + subnetConversions: make(map[ids.ID]SubnetConversion), }, nil } @@ -357,25 +357,21 @@ func (d *diff) SetSubnetOwner(subnetID ids.ID, owner fx.Owner) { d.subnetOwners[subnetID] = owner } -func (d *diff) GetSubnetConversion(subnetID ids.ID) (ids.ID, ids.ID, []byte, error) { +func (d *diff) GetSubnetConversion(subnetID ids.ID) (SubnetConversion, error) { if c, ok := d.subnetConversions[subnetID]; ok { - return c.ConversionID, c.ChainID, c.Addr, nil + return c, nil } // If the subnet conversion was not assigned in this diff, ask the parent state. parentState, ok := d.stateVersions.GetState(d.parentID) if !ok { - return ids.Empty, ids.Empty, nil, ErrMissingParentState + return SubnetConversion{}, ErrMissingParentState } return parentState.GetSubnetConversion(subnetID) } -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, - } +func (d *diff) SetSubnetConversion(subnetID ids.ID, c SubnetConversion) { + d.subnetConversions[subnetID] = c } func (d *diff) GetSubnetTransformation(subnetID ids.ID) (*txs.Tx, error) { @@ -578,7 +574,7 @@ func (d *diff) Apply(baseState Chain) error { baseState.SetSubnetOwner(subnetID, owner) } for subnetID, c := range d.subnetConversions { - baseState.SetSubnetConversion(subnetID, c.ConversionID, c.ChainID, c.Addr) + baseState.SetSubnetConversion(subnetID, c) } return nil } diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index 7441786ac1bf..013a918b60fa 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -777,65 +777,39 @@ func TestDiffSubnetConversion(t *testing.T) { require = require.New(t) state = newTestState(t, memdb.New()) subnetID = ids.GenerateTestID() - expectedConversion = subnetConversion{ + expectedConversion = SubnetConversion{ ConversionID: ids.GenerateTestID(), ChainID: ids.GenerateTestID(), Addr: []byte{1, 2, 3, 4}, } ) - conversionID, chainID, addr, err := state.GetSubnetConversion(subnetID) + actualConversion, err := state.GetSubnetConversion(subnetID) require.ErrorIs(err, database.ErrNotFound) - require.Zero(subnetConversion{ - ConversionID: conversionID, - ChainID: chainID, - Addr: addr, - }) + require.Zero(actualConversion) d, err := NewDiffOn(state) require.NoError(err) - conversionID, chainID, addr, err = d.GetSubnetConversion(subnetID) + actualConversion, err = d.GetSubnetConversion(subnetID) require.ErrorIs(err, database.ErrNotFound) - require.Zero(subnetConversion{ - ConversionID: conversionID, - ChainID: chainID, - Addr: addr, - }) + require.Zero(actualConversion) // Setting a subnet conversion should be reflected on diff not state - d.SetSubnetConversion(subnetID, expectedConversion.ConversionID, expectedConversion.ChainID, expectedConversion.Addr) - conversionID, chainID, addr, err = d.GetSubnetConversion(subnetID) + d.SetSubnetConversion(subnetID, expectedConversion) + actualConversion, err = d.GetSubnetConversion(subnetID) require.NoError(err) - require.Equal( - expectedConversion, - subnetConversion{ - ConversionID: conversionID, - ChainID: chainID, - Addr: addr, - }, - ) + require.Equal(expectedConversion, actualConversion) - conversionID, chainID, addr, err = state.GetSubnetConversion(subnetID) + actualConversion, err = state.GetSubnetConversion(subnetID) require.ErrorIs(err, database.ErrNotFound) - require.Zero(subnetConversion{ - ConversionID: conversionID, - ChainID: chainID, - Addr: addr, - }) + require.Zero(actualConversion) // State should reflect new subnet conversion after diff is applied require.NoError(d.Apply(state)) - conversionID, chainID, addr, err = state.GetSubnetConversion(subnetID) + actualConversion, err = state.GetSubnetConversion(subnetID) require.NoError(err) - require.Equal( - expectedConversion, - subnetConversion{ - ConversionID: conversionID, - ChainID: chainID, - Addr: addr, - }, - ) + require.Equal(expectedConversion, actualConversion) } func TestDiffStacking(t *testing.T) { diff --git a/vms/platformvm/state/mock_chain.go b/vms/platformvm/state/mock_chain.go index f4f8dc661b90..27daeae3a101 100644 --- a/vms/platformvm/state/mock_chain.go +++ b/vms/platformvm/state/mock_chain.go @@ -354,14 +354,12 @@ func (mr *MockChainMockRecorder) GetPendingValidator(subnetID, nodeID any) *gomo } // GetSubnetConversion mocks base method. -func (m *MockChain) GetSubnetConversion(subnetID ids.ID) (ids.ID, ids.ID, []byte, error) { +func (m *MockChain) GetSubnetConversion(subnetID ids.ID) (SubnetConversion, 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 + ret0, _ := ret[0].(SubnetConversion) + ret1, _ := ret[1].(error) + return ret0, ret1 } // GetSubnetConversion indicates an expected call of GetSubnetConversion. @@ -575,15 +573,15 @@ func (mr *MockChainMockRecorder) SetFeeState(f any) *gomock.Call { } // SetSubnetConversion mocks base method. -func (m *MockChain) SetSubnetConversion(subnetID, conversionID, chainID ids.ID, addr []byte) { +func (m *MockChain) SetSubnetConversion(subnetID ids.ID, c SubnetConversion) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SetSubnetConversion", subnetID, conversionID, chainID, addr) + m.ctrl.Call(m, "SetSubnetConversion", subnetID, c) } // SetSubnetConversion indicates an expected call of SetSubnetConversion. -func (mr *MockChainMockRecorder) SetSubnetConversion(subnetID, conversionID, chainID, addr any) *gomock.Call { +func (mr *MockChainMockRecorder) SetSubnetConversion(subnetID, c any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetConversion", reflect.TypeOf((*MockChain)(nil).SetSubnetConversion), subnetID, conversionID, chainID, addr) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetConversion", reflect.TypeOf((*MockChain)(nil).SetSubnetConversion), subnetID, c) } // SetSubnetOwner mocks base method. diff --git a/vms/platformvm/state/mock_diff.go b/vms/platformvm/state/mock_diff.go index bbeed71080b1..8732fc49b406 100644 --- a/vms/platformvm/state/mock_diff.go +++ b/vms/platformvm/state/mock_diff.go @@ -368,14 +368,12 @@ func (mr *MockDiffMockRecorder) GetPendingValidator(subnetID, nodeID any) *gomoc } // GetSubnetConversion mocks base method. -func (m *MockDiff) GetSubnetConversion(subnetID ids.ID) (ids.ID, ids.ID, []byte, error) { +func (m *MockDiff) GetSubnetConversion(subnetID ids.ID) (SubnetConversion, 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 + ret0, _ := ret[0].(SubnetConversion) + ret1, _ := ret[1].(error) + return ret0, ret1 } // GetSubnetConversion indicates an expected call of GetSubnetConversion. @@ -589,15 +587,15 @@ func (mr *MockDiffMockRecorder) SetFeeState(f any) *gomock.Call { } // SetSubnetConversion mocks base method. -func (m *MockDiff) SetSubnetConversion(subnetID, conversionID, chainID ids.ID, addr []byte) { +func (m *MockDiff) SetSubnetConversion(subnetID ids.ID, c SubnetConversion) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SetSubnetConversion", subnetID, conversionID, chainID, addr) + m.ctrl.Call(m, "SetSubnetConversion", subnetID, c) } // SetSubnetConversion indicates an expected call of SetSubnetConversion. -func (mr *MockDiffMockRecorder) SetSubnetConversion(subnetID, conversionID, chainID, addr any) *gomock.Call { +func (mr *MockDiffMockRecorder) SetSubnetConversion(subnetID, c any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetConversion", reflect.TypeOf((*MockDiff)(nil).SetSubnetConversion), subnetID, conversionID, chainID, addr) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetConversion", reflect.TypeOf((*MockDiff)(nil).SetSubnetConversion), subnetID, c) } // SetSubnetOwner mocks base method. diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index ed44205d9b49..a17593982572 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -558,14 +558,12 @@ func (mr *MockStateMockRecorder) GetStatelessBlock(blockID any) *gomock.Call { } // GetSubnetConversion mocks base method. -func (m *MockState) GetSubnetConversion(subnetID ids.ID) (ids.ID, ids.ID, []byte, error) { +func (m *MockState) GetSubnetConversion(subnetID ids.ID) (SubnetConversion, 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 + ret0, _ := ret[0].(SubnetConversion) + ret1, _ := ret[1].(error) + return ret0, ret1 } // GetSubnetConversion indicates an expected call of GetSubnetConversion. @@ -848,15 +846,15 @@ func (mr *MockStateMockRecorder) SetLastAccepted(blkID any) *gomock.Call { } // SetSubnetConversion mocks base method. -func (m *MockState) SetSubnetConversion(subnetID, conversionID, chainID ids.ID, addr []byte) { +func (m *MockState) SetSubnetConversion(subnetID ids.ID, c SubnetConversion) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SetSubnetConversion", subnetID, conversionID, chainID, addr) + m.ctrl.Call(m, "SetSubnetConversion", subnetID, c) } // SetSubnetConversion indicates an expected call of SetSubnetConversion. -func (mr *MockStateMockRecorder) SetSubnetConversion(subnetID, conversionID, chainID, addr any) *gomock.Call { +func (mr *MockStateMockRecorder) SetSubnetConversion(subnetID, c any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetConversion", reflect.TypeOf((*MockState)(nil).SetSubnetConversion), subnetID, conversionID, chainID, addr) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetConversion", reflect.TypeOf((*MockState)(nil).SetSubnetConversion), subnetID, c) } // SetSubnetOwner mocks base method. diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index a059323f6874..58c2056570bd 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -123,8 +123,8 @@ type Chain interface { GetSubnetOwner(subnetID ids.ID) (fx.Owner, error) SetSubnetOwner(subnetID ids.ID, owner fx.Owner) - GetSubnetConversion(subnetID ids.ID) (conversionID ids.ID, chainID ids.ID, addr []byte, err error) - SetSubnetConversion(subnetID ids.ID, conversionID ids.ID, chainID ids.ID, addr []byte) + GetSubnetConversion(subnetID ids.ID) (SubnetConversion, error) + SetSubnetConversion(subnetID ids.ID, c SubnetConversion) GetSubnetTransformation(subnetID ids.ID) (*txs.Tx, error) AddSubnetTransformation(transformSubnetTx *txs.Tx) @@ -366,8 +366,8 @@ 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 - subnetConversions map[ids.ID]subnetConversion // map of subnetID -> conversion of the subnet - subnetConversionCache cache.Cacher[ids.ID, subnetConversion] // cache of subnetID -> conversion + subnetConversions map[ids.ID]SubnetConversion // map of subnetID -> conversion of the subnet + subnetConversionCache cache.Cacher[ids.ID, SubnetConversion] // cache of subnetID -> conversion subnetConversionDB database.Database transformedSubnets map[ids.ID]*txs.Tx // map of subnetID -> transformSubnetTx @@ -441,7 +441,7 @@ type fxOwnerAndSize struct { size int } -type subnetConversion struct { +type SubnetConversion struct { ConversionID ids.ID `serialize:"true"` ChainID ids.ID `serialize:"true"` Addr []byte `serialize:"true"` @@ -556,10 +556,10 @@ func New( } subnetConversionDB := prefixdb.New(SubnetConversionPrefix, baseDB) - subnetConversionCache, err := metercacher.New[ids.ID, subnetConversion]( + subnetConversionCache, err := metercacher.New[ids.ID, SubnetConversion]( "subnet_conversion_cache", metricsReg, - cache.NewSizedLRU[ids.ID, subnetConversion](execCfg.SubnetConversionCacheSize, func(_ ids.ID, c subnetConversion) int { + cache.NewSizedLRU[ids.ID, SubnetConversion](execCfg.SubnetConversionCacheSize, func(_ ids.ID, c SubnetConversion) int { return 3*ids.IDLen + len(c.Addr) }), ) @@ -669,7 +669,7 @@ func New( subnetOwnerDB: subnetOwnerDB, subnetOwnerCache: subnetOwnerCache, - subnetConversions: make(map[ids.ID]subnetConversion), + subnetConversions: make(map[ids.ID]SubnetConversion), subnetConversionDB: subnetConversionDB, subnetConversionCache: subnetConversionCache, @@ -859,34 +859,30 @@ func (s *state) SetSubnetOwner(subnetID ids.ID, owner fx.Owner) { s.subnetOwners[subnetID] = owner } -func (s *state) GetSubnetConversion(subnetID ids.ID) (ids.ID, ids.ID, []byte, error) { +func (s *state) GetSubnetConversion(subnetID ids.ID) (SubnetConversion, error) { if c, ok := s.subnetConversions[subnetID]; ok { - return c.ConversionID, c.ChainID, c.Addr, nil + return c, nil } if c, ok := s.subnetConversionCache.Get(subnetID); ok { - return c.ConversionID, c.ChainID, c.Addr, nil + return c, nil } bytes, err := s.subnetConversionDB.Get(subnetID[:]) if err != nil { - return ids.Empty, ids.Empty, nil, err + return SubnetConversion{}, err } - var c subnetConversion + var c SubnetConversion if _, err := block.GenesisCodec.Unmarshal(bytes, &c); err != nil { - return ids.Empty, ids.Empty, nil, err + return SubnetConversion{}, err } s.subnetConversionCache.Put(subnetID, c) - return c.ConversionID, c.ChainID, c.Addr, nil + return c, nil } -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, - } +func (s *state) SetSubnetConversion(subnetID ids.ID, c SubnetConversion) { + s.subnetConversions[subnetID] = c } func (s *state) GetSubnetTransformation(subnetID ids.ID) (*txs.Tx, error) { diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index f2e0483a25c6..d29129500435 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1326,18 +1326,17 @@ func TestStateSubnetOwner(t *testing.T) { func TestStateSubnetConversion(t *testing.T) { tests := []struct { name string - setup func(s *state, subnetID ids.ID, c subnetConversion) + setup func(s *state, subnetID ids.ID, c SubnetConversion) }{ { name: "in-memory", - setup: func(s *state, subnetID ids.ID, c subnetConversion) { - s.SetSubnetConversion(subnetID, c.ConversionID, c.ChainID, c.Addr) + setup: func(s *state, subnetID ids.ID, c SubnetConversion) { + s.SetSubnetConversion(subnetID, c) }, }, { name: "cache", - setup: func(s *state, subnetID ids.ID, c subnetConversion) { - s.subnetConversionCache.Flush() + setup: func(s *state, subnetID ids.ID, c SubnetConversion) { s.subnetConversionCache.Put(subnetID, c) }, }, @@ -1348,33 +1347,22 @@ func TestStateSubnetConversion(t *testing.T) { require = require.New(t) state = newTestState(t, memdb.New()) subnetID = ids.GenerateTestID() - expectedConversion = subnetConversion{ + expectedConversion = SubnetConversion{ ConversionID: ids.GenerateTestID(), ChainID: ids.GenerateTestID(), Addr: []byte{'a', 'd', 'd', 'r'}, } ) - conversionID, chainID, addr, err := state.GetSubnetConversion(subnetID) + actualConversion, err := state.GetSubnetConversion(subnetID) require.ErrorIs(err, database.ErrNotFound) - require.Zero(subnetConversion{ - ConversionID: conversionID, - ChainID: chainID, - Addr: addr, - }) + require.Zero(actualConversion) test.setup(state, subnetID, expectedConversion) - conversionID, chainID, addr, err = state.GetSubnetConversion(subnetID) + actualConversion, err = state.GetSubnetConversion(subnetID) require.NoError(err) - require.Equal( - expectedConversion, - subnetConversion{ - ConversionID: conversionID, - ChainID: chainID, - Addr: addr, - }, - ) + require.Equal(expectedConversion, actualConversion) }) } } diff --git a/vms/platformvm/txs/executor/staker_tx_verification.go b/vms/platformvm/txs/executor/staker_tx_verification.go index 69b8cd567586..72ef8561ad43 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.GetSubnetConversion(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 9cc9eddf723b..1c08880a94e0 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -542,8 +542,15 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { // Produce the UTXOS avax.Produce(e.State, txID, tx.Outs) // Track the subnet conversion in the database - // TODO: Populate the conversionID - e.State.SetSubnetConversion(tx.Subnet, ids.Empty, tx.ChainID, tx.Address) + e.State.SetSubnetConversion( + tx.Subnet, + state.SubnetConversion{ + // TODO: Populate the conversionID + ConversionID: ids.Empty, + ChainID: tx.ChainID, + Addr: tx.Address, + }, + ) return nil } diff --git a/vms/platformvm/txs/executor/subnet_tx_verification.go b/vms/platformvm/txs/executor/subnet_tx_verification.go index 7466fd78227e..59acbe650491 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.GetSubnetConversion(subnetID) + _, err = chainState.GetSubnetConversion(subnetID) if err == nil { return nil, fmt.Errorf("%q %w", subnetID, errIsImmutable) } From f1b07d294a98b6035cdd5e8804ec602826fb857b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 20 Oct 2024 13:30:34 -0400 Subject: [PATCH 201/400] nit --- .../txs/executor/create_chain_test.go | 8 ++-- .../txs/executor/standard_tx_executor_test.go | 41 ++++++++++++++----- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/vms/platformvm/txs/executor/create_chain_test.go b/vms/platformvm/txs/executor/create_chain_test.go index cf3e0e8d2cf2..7f9919e4a8f5 100644 --- a/vms/platformvm/txs/executor/create_chain_test.go +++ b/vms/platformvm/txs/executor/create_chain_test.go @@ -288,9 +288,11 @@ func TestEtnaCreateChainTxInvalidWithManagedSubnet(t *testing.T) { stateDiff.SetSubnetConversion( subnetID, - ids.GenerateTestID(), - ids.GenerateTestID(), - []byte{'a', 'd', 'd', 'r', 'e', 's', 's'}, + state.SubnetConversion{ + ConversionID: ids.GenerateTestID(), + ChainID: ids.GenerateTestID(), + Addr: []byte("address"), + }, ) feeCalculator := state.PickFeeCalculator(env.config, builderDiff) diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 64686a1c744e..e3f6a67b21b3 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -915,9 +915,11 @@ func TestEtnaStandardTxExecutorAddSubnetValidator(t *testing.T) { onAcceptState.SetSubnetConversion( subnetID, - ids.GenerateTestID(), - ids.GenerateTestID(), - []byte{'a', 'd', 'd', 'r', 'e', 's', 's'}, + state.SubnetConversion{ + ConversionID: ids.GenerateTestID(), + ChainID: ids.GenerateTestID(), + Addr: []byte("address"), + }, ) executor := StandardTxExecutor{ @@ -1999,7 +2001,14 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { name: "attempted to remove subnet validator after subnet conversion has occurred", newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) - 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().GetSubnetConversion(env.unsignedTx.Subnet).Return( + state.SubnetConversion{ + ConversionID: ids.GenerateTestID(), + ChainID: ids.GenerateTestID(), + Addr: []byte("address"), + }, + nil, + ).AnyTimes() env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() cfg := &config.Config{ @@ -2483,7 +2492,14 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { { name: "invalid if subnet is converted", updateExecutor: func(e *StandardTxExecutor) { - e.State.SetSubnetConversion(subnetID, ids.GenerateTestID(), ids.GenerateTestID(), nil) + e.State.SetSubnetConversion( + subnetID, + state.SubnetConversion{ + ConversionID: ids.GenerateTestID(), + ChainID: ids.GenerateTestID(), + Addr: utils.RandomBytes(32), + }, + ) }, expectedErr: errIsImmutable, }, @@ -2571,12 +2587,17 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { require.Equal(expectedUTXO, utxo) } - stateConversionID, stateChainID, stateAddress, err := diff.GetSubnetConversion(subnetID) + stateConversion, err := diff.GetSubnetConversion(subnetID) require.NoError(err) - // TODO: Update this test when we populate the correct conversionID - require.Zero(stateConversionID) - require.Equal(chainID, stateChainID) - require.Equal(address, stateAddress) + require.Equal( + state.SubnetConversion{ + // TODO: Specify the correct conversionID + ConversionID: ids.Empty, + ChainID: chainID, + Addr: address, + }, + stateConversion, + ) }) } } From 7fb99c068b63d5937d098e088033cb009e394c1d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 20 Oct 2024 15:21:42 -0400 Subject: [PATCH 202/400] fix unit tests --- .../txs/executor/standard_tx_executor_test.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index e3f6a67b21b3..0301748101bc 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -2259,7 +2259,10 @@ 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().GetSubnetConversion(env.unsignedTx.Subnet).Return(ids.Empty, ids.Empty, nil, database.ErrNotFound).Times(1) + env.state.EXPECT().GetSubnetConversion(env.unsignedTx.Subnet).Return( + state.SubnetConversion{}, + 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( @@ -2298,7 +2301,14 @@ 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().GetSubnetConversion(env.unsignedTx.Subnet).Return(ids.GenerateTestID(), ids.GenerateTestID(), make([]byte, 20), nil) + env.state.EXPECT().GetSubnetConversion(env.unsignedTx.Subnet).Return( + state.SubnetConversion{ + ConversionID: ids.GenerateTestID(), + ChainID: ids.GenerateTestID(), + Addr: 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) @@ -2333,7 +2343,10 @@ 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().GetSubnetConversion(env.unsignedTx.Subnet).Return(ids.Empty, ids.Empty, nil, database.ErrNotFound).Times(1) + env.state.EXPECT().GetSubnetConversion(env.unsignedTx.Subnet).Return( + state.SubnetConversion{}, + 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( From f31cfc670423a7d02b31f5b3c2eb19ac98c6e523 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 20 Oct 2024 16:36:50 -0400 Subject: [PATCH 203/400] Add SoV Excess to P-chain state --- .../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 | 25 +++++++++++++++++ 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 | 27 +++++++++++++++++++ vms/platformvm/state/state_test.go | 16 +++++++++++ 10 files changed, 166 insertions(+) diff --git a/vms/platformvm/block/executor/proposal_block_test.go b/vms/platformvm/block/executor/proposal_block_test.go index 4a4154b02e50..c0a597d77335 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) { // setup state to validate proposal block transaction onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() + onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() onParentAccept.EXPECT().GetCurrentStakerIterator().Return( @@ -162,6 +163,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { onParentAccept := state.NewMockDiff(ctrl) onParentAccept.EXPECT().GetTimestamp().Return(parentTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() + onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(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 8e62937c9239..d9ad860d3d3b 100644 --- a/vms/platformvm/block/executor/standard_block_test.go +++ b/vms/platformvm/block/executor/standard_block_test.go @@ -59,6 +59,7 @@ func TestApricotStandardBlockTimeVerification(t *testing.T) { chainTime := env.clk.Time().Truncate(time.Second) onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() + onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() // wrong height @@ -134,6 +135,7 @@ func TestBanffStandardBlockTimeVerification(t *testing.T) { onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() + onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() txID := ids.GenerateTestID() diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index f57b8fb4ed58..a076616701f2 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -103,6 +103,7 @@ func TestVerifierVisitProposalBlock(t *testing.T) { // One call for each of onCommitState and onAbortState. parentOnAcceptState.EXPECT().GetTimestamp().Return(timestamp).Times(2) parentOnAcceptState.EXPECT().GetFeeState().Return(gas.State{}).Times(2) + parentOnAcceptState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(2) parentOnAcceptState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(2) backend := &backend{ @@ -335,6 +336,7 @@ func TestVerifierVisitStandardBlock(t *testing.T) { timestamp := time.Now() parentState.EXPECT().GetTimestamp().Return(timestamp).Times(1) parentState.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + parentState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) parentState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) mempool.EXPECT().Remove(apricotBlk.Txs()).Times(1) @@ -597,6 +599,7 @@ func TestBanffAbortBlockTimestampChecks(t *testing.T) { s.EXPECT().GetLastAccepted().Return(parentID).Times(3) s.EXPECT().GetTimestamp().Return(parentTime).Times(3) s.EXPECT().GetFeeState().Return(gas.State{}).Times(3) + s.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(3) s.EXPECT().GetAccruedFees().Return(uint64(0)).Times(3) onDecisionState, err := state.NewDiff(parentID, backend) @@ -695,6 +698,7 @@ func TestBanffCommitBlockTimestampChecks(t *testing.T) { s.EXPECT().GetLastAccepted().Return(parentID).Times(3) s.EXPECT().GetTimestamp().Return(parentTime).Times(3) s.EXPECT().GetFeeState().Return(gas.State{}).Times(3) + s.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(3) s.EXPECT().GetAccruedFees().Return(uint64(0)).Times(3) onDecisionState, err := state.NewDiff(parentID, backend) @@ -811,6 +815,7 @@ func TestVerifierVisitStandardBlockWithDuplicateInputs(t *testing.T) { parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) parentState.EXPECT().GetTimestamp().Return(timestamp).Times(1) parentState.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + parentState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) parentState.EXPECT().GetAccruedFees().Return(uint64(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 9fe6a62363c0..da73854346ea 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 // Subnet ID --> supply of native asset of the subnet @@ -80,6 +81,7 @@ func NewDiff( stateVersions: stateVersions, timestamp: parentState.GetTimestamp(), feeState: parentState.GetFeeState(), + sovExcess: parentState.GetSoVExcess(), accruedFees: parentState.GetAccruedFees(), expiryDiff: newExpiryDiff(), subnetOwners: make(map[ids.ID]fx.Owner), @@ -117,6 +119,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 } @@ -482,6 +492,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 013a918b60fa..e12a60808542 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -68,6 +68,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) @@ -270,6 +288,7 @@ func TestDiffCurrentValidator(t *testing.T) { // Called in NewDiffOn state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) d, err := NewDiffOn(state) @@ -305,6 +324,7 @@ func TestDiffPendingValidator(t *testing.T) { // Called in NewDiffOn state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) d, err := NewDiffOn(state) @@ -346,6 +366,7 @@ func TestDiffCurrentDelegator(t *testing.T) { // Called in NewDiffOn state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) d, err := NewDiffOn(state) @@ -390,6 +411,7 @@ func TestDiffPendingDelegator(t *testing.T) { // Called in NewDiffOn state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) d, err := NewDiffOn(state) @@ -528,6 +550,7 @@ func TestDiffTx(t *testing.T) { // Called in NewDiffOn state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) d, err := NewDiffOn(state) @@ -626,6 +649,7 @@ func TestDiffUTXO(t *testing.T) { // Called in NewDiffOn state.EXPECT().GetTimestamp().Return(time.Now()).Times(1) state.EXPECT().GetFeeState().Return(gas.State{}).Times(1) + state.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) state.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) d, err := NewDiffOn(state) @@ -703,6 +727,7 @@ func assertChainsEqual(t *testing.T, expected, actual Chain) { require.Equal(expected.GetTimestamp(), actual.GetTimestamp()) require.Equal(expected.GetFeeState(), actual.GetFeeState()) + require.Equal(expected.GetSoVExcess(), actual.GetSoVExcess()) require.Equal(expected.GetAccruedFees(), actual.GetAccruedFees()) expectedCurrentSupply, err := expected.GetCurrentSupply(constants.PrimaryNetworkID) diff --git a/vms/platformvm/state/mock_chain.go b/vms/platformvm/state/mock_chain.go index 27daeae3a101..56c495924511 100644 --- a/vms/platformvm/state/mock_chain.go +++ b/vms/platformvm/state/mock_chain.go @@ -353,6 +353,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)) +} + // GetSubnetConversion mocks base method. func (m *MockChain) GetSubnetConversion(subnetID ids.ID) (SubnetConversion, error) { m.ctrl.T.Helper() @@ -572,6 +586,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) +} + // SetSubnetConversion mocks base method. func (m *MockChain) SetSubnetConversion(subnetID ids.ID, c SubnetConversion) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/mock_diff.go b/vms/platformvm/state/mock_diff.go index 8732fc49b406..b8362386af96 100644 --- a/vms/platformvm/state/mock_diff.go +++ b/vms/platformvm/state/mock_diff.go @@ -367,6 +367,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)) +} + // GetSubnetConversion mocks base method. func (m *MockDiff) GetSubnetConversion(subnetID ids.ID) (SubnetConversion, error) { m.ctrl.T.Helper() @@ -586,6 +600,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) +} + // SetSubnetConversion mocks base method. func (m *MockDiff) SetSubnetConversion(subnetID ids.ID, c SubnetConversion) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index a17593982572..cb05f54fc6f7 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -527,6 +527,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() @@ -845,6 +859,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) +} + // SetSubnetConversion mocks base method. func (m *MockState) SetSubnetConversion(subnetID ids.ID, c SubnetConversion) { m.ctrl.T.Helper() diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 58c2056570bd..53b109c23249 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -87,6 +87,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") @@ -110,6 +111,9 @@ type Chain interface { GetFeeState() gas.State SetFeeState(f gas.State) + GetSoVExcess() gas.Gas + SetSoVExcess(e gas.Gas) + GetAccruedFees() uint64 SetAccruedFees(f uint64) @@ -289,6 +293,7 @@ type stateBlk struct { * |-- blocksReindexedKey -> nil * |-- timestampKey -> timestamp * |-- feeStateKey -> feeState + * |-- sovExcessKey -> sovExcess * |-- accruedFeesKey -> accruedFees * |-- currentSupplyKey -> currentSupply * |-- lastAcceptedKey -> lastAccepted @@ -386,6 +391,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. @@ -1091,6 +1097,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 } @@ -1391,6 +1405,13 @@ func (s *state) loadMetadata() error { s.persistedFeeState = feeState s.SetFeeState(feeState) + sovExcess, err := database.WithDefault(database.GetUInt64, s.singletonDB, SoVExcessKey, 0) + if err != nil { + return err + } + s.persistedSOVExcess = gas.Gas(sovExcess) + s.SetSoVExcess(gas.Gas(sovExcess)) + accruedFees, err := database.WithDefault(database.GetUInt64, s.singletonDB, AccruedFeesKey, 0) if err != nil { return err @@ -2439,6 +2460,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) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index d29129500435..6204540bd615 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1474,6 +1474,22 @@ func TestStateFeeStateCommitAndLoad(t *testing.T) { require.Equal(expectedFeeState, s.GetFeeState()) } +// Verify that committing the state writes the sov excess to the database and +// that loading the state fetches the sov excess from the database. +func TestStateSoVExcessCommitAndLoad(t *testing.T) { + require := require.New(t) + + db := memdb.New() + s := newTestState(t, db) + + const expectedSoVExcess gas.Gas = 10 + s.SetSoVExcess(expectedSoVExcess) + require.NoError(s.Commit()) + + s = newTestState(t, db) + require.Equal(expectedSoVExcess, s.GetSoVExcess()) +} + // Verify that committing the state writes the accrued fees to the database and // that loading the state fetches the accrued fees from the database. func TestStateAccruedFeesCommitAndLoad(t *testing.T) { From f82ef2c8b8764307b16dc4a653301f8a57eedd7f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 20 Oct 2024 18:16:32 -0400 Subject: [PATCH 204/400] merged --- vms/platformvm/config/execution_config.go | 1 - 1 file changed, 1 deletion(-) diff --git a/vms/platformvm/config/execution_config.go b/vms/platformvm/config/execution_config.go index 2dbfdc9b5f99..eaa205e1e6d3 100644 --- a/vms/platformvm/config/execution_config.go +++ b/vms/platformvm/config/execution_config.go @@ -37,7 +37,6 @@ type ExecutionConfig struct { BlockIDCacheSize int `json:"block-id-cache-size"` FxOwnerCacheSize int `json:"fx-owner-cache-size"` SubnetConversionCacheSize int `json:"subnet-conversion-cache-size"` - SubnetManagerCacheSize int `json:"subnet-manager-cache-size"` ChecksumsEnabled bool `json:"checksums-enabled"` MempoolPruneFrequency time.Duration `json:"mempool-prune-frequency"` } From dc900431cf6f0fef4bdc2f440eaa634a2df031f6 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 20 Oct 2024 18:18:40 -0400 Subject: [PATCH 205/400] fix merge --- vms/platformvm/txs/executor/standard_tx_executor.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 2a03ec90cc97..497928a9c2aa 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -703,15 +703,15 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return err } - _, expectedChainID, expectedAddress, err := e.State.GetSubnetConversion(msg.SubnetID) + subnetConversion, err := e.State.GetSubnetConversion(msg.SubnetID) if err != nil { return err } - if warpMessage.SourceChainID != expectedChainID { - return fmt.Errorf("expected chainID %s but got %s", expectedChainID, warpMessage.SourceChainID) + if warpMessage.SourceChainID != subnetConversion.ChainID { + return fmt.Errorf("expected chainID %s but got %s", subnetConversion.ChainID, warpMessage.SourceChainID) } - if !bytes.Equal(addressedCall.SourceAddress, expectedAddress) { - return fmt.Errorf("expected address %s but got %s", expectedAddress, addressedCall.SourceAddress) + if !bytes.Equal(addressedCall.SourceAddress, subnetConversion.Addr) { + return fmt.Errorf("expected address %s but got %s", subnetConversion.Addr, addressedCall.SourceAddress) } currentTimestampUnix := uint64(currentTimestamp.Unix()) From 2d722fa2dfa4800119d4729c6befbbf948aac02f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 20 Oct 2024 18:20:01 -0400 Subject: [PATCH 206/400] fix merge --- vms/platformvm/txs/executor/standard_tx_executor.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 12a29372a358..fc87daeefb3d 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -875,15 +875,15 @@ 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.GetSubnetConversion(sov.SubnetID) + subnetConversion, err := e.State.GetSubnetConversion(sov.SubnetID) if err != nil { return err } - if warpMessage.SourceChainID != expectedChainID { - return fmt.Errorf("expected chainID %s but got %s", expectedChainID, warpMessage.SourceChainID) + if warpMessage.SourceChainID != subnetConversion.ChainID { + return fmt.Errorf("expected chainID %s but got %s", subnetConversion.ChainID, warpMessage.SourceChainID) } - if !bytes.Equal(addressedCall.SourceAddress, expectedAddress) { - return fmt.Errorf("expected address %s but got %s", expectedAddress, addressedCall.SourceAddress) + if !bytes.Equal(addressedCall.SourceAddress, subnetConversion.Addr) { + return fmt.Errorf("expected address %s but got %s", subnetConversion.Addr, addressedCall.SourceAddress) } txID := e.Tx.ID() From 58d5b8ddf85fe3a4a9a9b0739e6d3844e6d1ea9f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 21 Oct 2024 18:42:17 -0400 Subject: [PATCH 207/400] Populate BLS key diffs for subnet validators --- vms/platformvm/state/state.go | 317 ++++++++++++++++++++-------------- 1 file changed, 186 insertions(+), 131 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 58c2056570bd..a34b587a6732 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -59,8 +59,9 @@ const ( var ( _ State = (*state)(nil) - errValidatorSetAlreadyPopulated = errors.New("validator set already populated") - errIsNotSubnet = errors.New("is not a subnet") + errValidatorSetAlreadyPopulated = errors.New("validator set already populated") + errIsNotSubnet = errors.New("is not a subnet") + errMissingPrimaryNetworkValidator = errors.New("missing primary network validator") BlockIDPrefix = []byte("blockID") BlockPrefix = []byte("block") @@ -2007,164 +2008,218 @@ func (s *state) writeExpiry() error { func (s *state) writeCurrentStakers(updateValidators bool, height uint64, codecVersion uint16) error { for subnetID, validatorDiffs := range s.currentStakers.validatorDiffs { + // We must write the primary network stakers last because writing subnet + // validator diffs may depend on the primary network validator diffs to + // inherit the public keys. + 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 { + 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 ErrMissingPrimaryNetworkValidator } - - s.validatorState.DeleteValidatorMetadata(nodeID, subnetID) } - 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 } From 273fbbecd3da929db6d00bab40511884efe5ee0c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 21 Oct 2024 18:50:47 -0400 Subject: [PATCH 208/400] Populate BLS key diffs for subnet validators --- vms/platformvm/state/state.go | 12 +- vms/platformvm/state/state_test.go | 351 +++++++++++++++++---------- vms/platformvm/validators/manager.go | 8 +- 3 files changed, 241 insertions(+), 130 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index a34b587a6732..bed7b4746fbe 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -190,6 +190,7 @@ type State interface { validators map[ids.NodeID]*validators.GetValidatorOutput, startHeight uint64, endHeight uint64, + subnetID ids.ID, ) error SetHeight(height uint64) @@ -1244,10 +1245,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() @@ -2101,8 +2103,10 @@ func (s *state) writeCurrentStakersSubnetDiff( // writing. pk = vdr.validator.PublicKey } else { - // This should never happen. - return ErrMissingPrimaryNetworkValidator + // This should never happen as the primary network diffs are + // written last and subnet validator times must be a subset + // of the primary network validator times. + return errMissingPrimaryNetworkValidator } } diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index d29129500435..ba8dbd65481f 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -28,6 +28,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/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -996,35 +997,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 @@ -1037,101 +1046,176 @@ 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 { - for _, added := range diff.addedValidators { - added := added - require.NoError(state.PutCurrentValidator(&added)) - } - for _, added := range diff.addedDelegators { - added := added - state.PutCurrentDelegator(&added) + d, err := NewDiffOn(state) + require.NoError(err) + + type subnetIDNodeID struct { + subnetID ids.ID + nodeID ids.NodeID } - for _, removed := range diff.removedDelegators { - removed := removed - state.DeleteCurrentDelegator(&removed) + var expectedValidators set.Set[subnetIDNodeID] + for _, added := range diff.addedValidators { + 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) @@ -1142,37 +1226,84 @@ func TestStateAddRemoveValidator(t *testing.T) { require.ErrorIs(err, database.ErrNotFound) } + primaryValidatorSet := state.validators.GetMap(constants.PrimaryNetworkID) + delete(primaryValidatorSet, defaultValidatorNodeID) // Ignore the genesis validator + require.Equal(diff.expectedPrimaryValidatorSet, primaryValidatorSet) + + require.Equal(diff.expectedSubnetValidatorSet, state.validators.GetMap(subnetID)) + for i := 0; i < currentIndex; i++ { prevDiff := diffs[i] prevHeight := uint64(i + 1) - primaryValidatorSet := copyValidatorSet(diff.expectedPrimaryValidatorSet) - require.NoError(state.ApplyValidatorWeightDiffs( - context.Background(), - primaryValidatorSet, - currentHeight, - prevHeight+1, - constants.PrimaryNetworkID, - )) - requireEqualWeightsValidatorSet(require, prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) - - require.NoError(state.ApplyValidatorPublicKeyDiffs( - context.Background(), - primaryValidatorSet, - currentHeight, - prevHeight+1, - )) - requireEqualPublicKeysValidatorSet(require, prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) - - subnetValidatorSet := copyValidatorSet(diff.expectedSubnetValidatorSet) - require.NoError(state.ApplyValidatorWeightDiffs( - context.Background(), - subnetValidatorSet, - currentHeight, - prevHeight+1, - subnetID, - )) - requireEqualWeightsValidatorSet(require, prevDiff.expectedSubnetValidatorSet, subnetValidatorSet) + { + primaryValidatorSet := copyValidatorSet(diff.expectedPrimaryValidatorSet) + require.NoError(state.ApplyValidatorWeightDiffs( + context.Background(), + primaryValidatorSet, + currentHeight, + prevHeight+1, + constants.PrimaryNetworkID, + )) + require.NoError(state.ApplyValidatorPublicKeyDiffs( + context.Background(), + primaryValidatorSet, + currentHeight, + prevHeight+1, + constants.PrimaryNetworkID, + )) + require.Equal(prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) + } + + { + legacySubnetValidatorSet := copyValidatorSet(diff.expectedSubnetValidatorSet) + require.NoError(state.ApplyValidatorWeightDiffs( + context.Background(), + legacySubnetValidatorSet, + currentHeight, + prevHeight+1, + subnetID, + )) + + // Update the public keys of the subnet validators with the current + // primary network validator public keys + for nodeID, vdr := range legacySubnetValidatorSet { + if primaryVdr, ok := diff.expectedPrimaryValidatorSet[nodeID]; ok { + vdr.PublicKey = primaryVdr.PublicKey + } else { + vdr.PublicKey = nil + } + } + + require.NoError(state.ApplyValidatorPublicKeyDiffs( + context.Background(), + legacySubnetValidatorSet, + currentHeight, + prevHeight+1, + constants.PrimaryNetworkID, + )) + require.Equal(prevDiff.expectedSubnetValidatorSet, legacySubnetValidatorSet) + } + + { + subnetValidatorSet := copyValidatorSet(diff.expectedSubnetValidatorSet) + require.NoError(state.ApplyValidatorWeightDiffs( + context.Background(), + subnetValidatorSet, + currentHeight, + prevHeight+1, + subnetID, + )) + + require.NoError(state.ApplyValidatorPublicKeyDiffs( + context.Background(), + subnetValidatorSet, + currentHeight, + prevHeight+1, + subnetID, + )) + require.Equal(prevDiff.expectedSubnetValidatorSet, subnetValidatorSet) + } } } } @@ -1188,36 +1319,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 7f1ea5ea6407..142db3e7635c 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 } @@ -348,6 +350,10 @@ func (m *manager) makeSubnetValidatorSet( subnetValidatorSet, currentHeight, lastDiffHeight, + // TODO: Etna introduces L1s whose validators specify their own public + // keys, rather than inheriting them from the primary network. + // Therefore, this will need to use the subnetID after Etna. + constants.PrimaryNetworkID, ) return subnetValidatorSet, currentHeight, err } From 290ef974191e6ebef1660b69c391a494e2ac9455 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 21 Oct 2024 20:31:16 -0400 Subject: [PATCH 209/400] Update mocks --- vms/platformvm/state/mock_state.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index a17593982572..9d2568422517 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. From 9155e1fd37f436f772568640f35cc813c4405c21 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 21 Oct 2024 20:34:50 -0400 Subject: [PATCH 210/400] Fix tests --- vms/platformvm/state/state_test.go | 850 ++++++++++------------------- 1 file changed, 283 insertions(+), 567 deletions(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index ba8dbd65481f..419586f7871c 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -5,7 +5,6 @@ package state import ( "context" - "fmt" "math" "math/rand" "sync" @@ -28,6 +27,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" @@ -110,620 +110,336 @@ 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) - - // 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) + 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 that node duly track stakers uptimes - checkValidatorUptimes func(*require.Assertions, *state, *Staker) + unsignedAddPrimaryNetworkValidator := createPermissionlessValidatorTx(t, constants.PrimaryNetworkID, primaryValidatorData) + addPrimaryNetworkValidator := &txs.Tx{Unsigned: unsignedAddPrimaryNetworkValidator} + require.NoError(t, addPrimaryNetworkValidator.Initialize(txs.Codec)) - // 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() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(endTime), - Wght: 1234, - } - validatorReward uint64 = 5678 - ) + primaryNetworkPendingValidatorStaker, err := NewPendingStaker( + addPrimaryNetworkValidator.ID(), + unsignedAddPrimaryNetworkValidator, + ) + require.NoError(t, err) - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) + primaryNetworkCurrentValidatorStaker, err := NewCurrentStaker( + addPrimaryNetworkValidator.ID(), + unsignedAddPrimaryNetworkValidator, + primaryValidatorStartTime, + primaryValidatorReward, + ) + require.NoError(t, err) - staker, err := NewCurrentStaker( - addPermValTx.ID(), - utx, - time.Unix(startTime, 0), - validatorReward, - ) - r.NoError(err) + unsignedAddPrimaryNetworkDelegator := createPermissionlessDelegatorTx(constants.PrimaryNetworkID, primaryDelegatorData) + addPrimaryNetworkDelegator := &txs.Tx{Unsigned: unsignedAddPrimaryNetworkDelegator} + require.NoError(t, addPrimaryNetworkDelegator.Initialize(txs.Codec)) - 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 - - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, - } - delegatorReward uint64 = 5432 - ) + primaryNetworkPendingDelegatorStaker, err := NewPendingStaker( + addPrimaryNetworkDelegator.ID(), + unsignedAddPrimaryNetworkDelegator, + ) + require.NoError(t, err) - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) + primaryNetworkCurrentDelegatorStaker, err := NewCurrentStaker( + addPrimaryNetworkDelegator.ID(), + unsignedAddPrimaryNetworkDelegator, + primaryDelegatorStartTime, + primaryDelegatorReward, + ) + require.NoError(t, err) - val, err := NewCurrentStaker( - addPermValTx.ID(), - utxVal, - time.Unix(valStartTime, 0), - validatorReward, - ) - r.NoError(err) + tests := map[string]struct { + initialStakers []*Staker + initialTxs []*txs.Tx - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) + // Staker to insert or remove + staker *Staker + tx *txs.Tx // If tx is nil, the staker is being removed - del, err := NewCurrentStaker( - addPermDelTx.ID(), - utxDel, - time.Unix(delStartTime, 0), - delegatorReward, - ) - r.NoError(err) + // Check that the staker is duly stored/removed in P-chain state + expectedCurrentValidator *Staker + expectedPendingValidator *Staker + expectedCurrentDelegators []*Staker + expectedPendingDelegators []*Staker - r.NoError(s.PutCurrentValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + // Check that the validator entry has been set correctly in the + // in-memory validator set. + expectedValidatorSetOutput *validators.GetValidatorOutput - 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 - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - retrievedStaker, err := s.GetPendingValidator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.Equal(staker, retrievedStaker) - }, - 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) - }, - 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) + "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, }, - 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) + }, + "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, }, + expectedPublicKeyDiff: maybe.Some(primaryNetworkCurrentValidatorStaker.PublicKey), }, - "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)) - - 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 - r.NoError(s.Commit()) - - s.PutPendingDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - - return del + "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, }, - 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) + }, + "delete pending primary network validator": { + initialStakers: []*Staker{primaryNetworkPendingValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkPendingValidatorStaker, + }, + "delete pending primary network delegator": { + initialStakers: []*Staker{ + primaryNetworkPendingValidatorStaker, + primaryNetworkPendingDelegatorStaker, }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) + initialTxs: []*txs.Tx{ + addPrimaryNetworkValidator, + addPrimaryNetworkDelegator, }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, + staker: primaryNetworkPendingDelegatorStaker, + expectedPendingValidator: primaryNetworkPendingValidatorStaker, }, - "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() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(endTime), - Wght: 1234, - } - validatorReward uint64 = 5678 - ) + } - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) + for name, test := range tests { + t.Run(name, func(t *testing.T) { + require := require.New(t) - staker, err := NewCurrentStaker( - addPermValTx.ID(), - utx, - time.Unix(startTime, 0), - validatorReward, - ) - r.NoError(err) + db := memdb.New() + state := newTestState(t, db) + + // 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) + } - r.NoError(s.PutCurrentValidator(staker)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + state.SetHeight(0) + require.NoError(state.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) + // create and store the staker under test + switch { + case test.staker.Priority.IsCurrentValidator(): + if test.tx != nil { + require.NoError(state.PutCurrentValidator(test.staker)) } else { - r.ErrorIs(err, database.ErrNotFound) + state.DeleteCurrentValidator(test.staker) } - }, - }, - "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)) + 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) + } - del, err := NewCurrentStaker( - addPermDelTx.ID(), - utxDel, - time.Unix(delStartTime, 0), - delegatorReward, - ) - r.NoError(err) + state.SetHeight(1) + require.NoError(state.Commit()) - r.NoError(s.PutCurrentValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker + // 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) - s.PutCurrentDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + // 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) + + // Current validators should also have uptimes + upDuration, lastUpdated, err := state.GetUptime(currentValidator.NodeID) + require.NoError(err) + require.Zero(upDuration) + require.Equal(currentValidator.StartTime, lastUpdated) + } - s.DeleteCurrentDelegator(del) - r.NoError(s.Commit()) + pendingValidator, err := state.GetPendingValidator(test.staker.SubnetID, test.staker.NodeID) + if test.expectedPendingValidator == nil { + require.ErrorIs(err, database.ErrNotFound) + } else { + require.NoError(err) + require.Equal(test.expectedPendingValidator, pendingValidator) + } - 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) } From 803d0c4d777aa587949df49f40b21b17cacafeed Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 22 Oct 2024 07:07:59 -0400 Subject: [PATCH 211/400] nit --- vms/platformvm/state/state_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 419586f7871c..29d37c15a341 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -428,7 +428,7 @@ func TestPersistStakers(t *testing.T) { require.Equal(expectedPublicKeyDiff, bls.PublicKeyFromValidUncompressedBytes(publicKeyDiffBytes)) } - // re-load the state from disk + // re-load the state from disk for the second iteration state = newTestState(t, db) } }) From a2c777337e26d7e63426644d61b00c8e0d21a81c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 22 Oct 2024 08:57:04 -0400 Subject: [PATCH 212/400] Update test and populate public keys during startup --- vms/platformvm/state/state.go | 21 ++++++--- vms/platformvm/state/state_test.go | 72 ++++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index bed7b4746fbe..8a81931f1111 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1718,19 +1718,28 @@ func (s *state) loadPendingValidators() error { // Invariant: initValidatorSets requires loadCurrentValidators to have already // been called. func (s *state) initValidatorSets() error { - 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 fmt.Errorf("%w: %s", errMissingPrimaryNetworkValidator, nodeID) + } + + 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 { @@ -2106,7 +2115,7 @@ func (s *state) writeCurrentStakersSubnetDiff( // This should never happen as the primary network diffs are // written last and subnet validator times must be a subset // of the primary network validator times. - return errMissingPrimaryNetworkValidator + return fmt.Errorf("%w: %s", errMissingPrimaryNetworkValidator, nodeID) } } diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 29d37c15a341..b725761bab9a 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -117,10 +117,10 @@ func TestPersistStakers(t *testing.T) { primaryValidatorDuration = 28 * 24 * time.Hour primaryDelegatorDuration = 14 * 24 * time.Hour subnetValidatorDuration = 21 * 24 * time.Hour - subnetDelegatorDuration = 14 * 24 * time.Hour primaryValidatorReward = iota primaryDelegatorReward + subnetValidatorReward ) var ( primaryValidatorStartTime = time.Now().Truncate(time.Second) @@ -131,6 +131,10 @@ func TestPersistStakers(t *testing.T) { primaryDelegatorEndTime = primaryDelegatorStartTime.Add(primaryDelegatorDuration) primaryDelegatorEndTimeUnix = uint64(primaryDelegatorEndTime.Unix()) + subnetValidatorStartTime = primaryValidatorStartTime + subnetValidatorEndTime = subnetValidatorStartTime.Add(subnetValidatorDuration) + subnetValidatorEndTimeUnix = uint64(subnetValidatorEndTime.Unix()) + primaryValidatorData = txs.Validator{ NodeID: ids.GenerateTestNodeID(), End: primaryValidatorEndTimeUnix, @@ -141,6 +145,13 @@ func TestPersistStakers(t *testing.T) { End: primaryDelegatorEndTimeUnix, Wght: 6789, } + subnetValidatorData = txs.Validator{ + NodeID: primaryValidatorData.NodeID, + End: subnetValidatorEndTimeUnix, + Wght: 9876, + } + + subnetID = ids.GenerateTestID() ) unsignedAddPrimaryNetworkValidator := createPermissionlessValidatorTx(t, constants.PrimaryNetworkID, primaryValidatorData) @@ -179,6 +190,18 @@ func TestPersistStakers(t *testing.T) { ) require.NoError(t, err) + unsignedAddSubnetValidator := createPermissionlessValidatorTx(t, subnetID, subnetValidatorData) + addSubnetValidator := &txs.Tx{Unsigned: unsignedAddSubnetValidator} + require.NoError(t, addSubnetValidator.Initialize(txs.Codec)) + + subnetCurrentValidatorStaker, err := NewCurrentStaker( + addSubnetValidator.ID(), + unsignedAddSubnetValidator, + subnetValidatorStartTime, + subnetValidatorReward, + ) + require.NoError(t, err) + tests := map[string]struct { initialStakers []*Staker initialTxs []*txs.Tx @@ -246,6 +269,23 @@ func TestPersistStakers(t *testing.T) { expectedPendingValidator: primaryNetworkPendingValidatorStaker, expectedPendingDelegators: []*Staker{primaryNetworkPendingDelegatorStaker}, }, + "add current subnet validator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: subnetCurrentValidatorStaker, + tx: addSubnetValidator, + expectedCurrentValidator: subnetCurrentValidatorStaker, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: subnetCurrentValidatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: subnetCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: false, + Amount: subnetCurrentValidatorStaker.Weight, + }, + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](nil), + }, "delete current primary network validator": { initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, @@ -294,6 +334,16 @@ func TestPersistStakers(t *testing.T) { staker: primaryNetworkPendingDelegatorStaker, expectedPendingValidator: primaryNetworkPendingValidatorStaker, }, + "delete current subnet validator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker, subnetCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator, addSubnetValidator}, + staker: subnetCurrentValidatorStaker, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: true, + Amount: subnetCurrentValidatorStaker.Weight, + }, + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](primaryNetworkCurrentValidatorStaker.PublicKey), + }, } for name, test := range tests { @@ -364,18 +414,22 @@ func TestPersistStakers(t *testing.T) { if test.expectedCurrentValidator == nil { require.ErrorIs(err, database.ErrNotFound) - // Only current validators should have uptimes - _, _, err := state.GetUptime(test.staker.NodeID) - require.ErrorIs(err, database.ErrNotFound) + if test.staker.SubnetID == constants.PrimaryNetworkID { + // Uptimes are only considered for primary network validators + _, _, err := state.GetUptime(test.staker.NodeID) + require.ErrorIs(err, database.ErrNotFound) + } } else { require.NoError(err) require.Equal(test.expectedCurrentValidator, currentValidator) - // Current validators should also have uptimes - upDuration, lastUpdated, err := state.GetUptime(currentValidator.NodeID) - require.NoError(err) - require.Zero(upDuration) - require.Equal(currentValidator.StartTime, lastUpdated) + if test.staker.SubnetID == constants.PrimaryNetworkID { + // Uptimes are only considered for primary network validators + upDuration, lastUpdated, err := state.GetUptime(currentValidator.NodeID) + require.NoError(err) + require.Zero(upDuration) + require.Equal(currentValidator.StartTime, lastUpdated) + } } pendingValidator, err := state.GetPendingValidator(test.staker.SubnetID, test.staker.NodeID) From 95c42a16942d6ba0a8a67a0ebd29ed7129310efa Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 23 Oct 2024 11:39:45 -0400 Subject: [PATCH 213/400] comment --- vms/platformvm/state/state.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 8a81931f1111..725b9e010c40 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1726,6 +1726,8 @@ func (s *state) initValidatorSets() error { } for nodeID, subnetValidator := range subnetValidators { + // The subnet validator's Public Key is inherited from the + // corresponding primary network validator. primaryValidator, ok := primaryNetworkValidators[nodeID] if !ok { return fmt.Errorf("%w: %s", errMissingPrimaryNetworkValidator, nodeID) From 21cdc32e2b6508a09ff390fdee7f7b5bf90620fe Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 24 Oct 2024 10:28:07 -0400 Subject: [PATCH 214/400] Update P-chain state staker tests --- vms/platformvm/state/state_test.go | 1203 ++++++++++++---------------- 1 file changed, 514 insertions(+), 689 deletions(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 6204540bd615..e9096af13a1c 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -5,7 +5,6 @@ package state import ( "context" - "fmt" "math" "math/rand" "sync" @@ -28,6 +27,8 @@ 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" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -109,620 +110,387 @@ func TestStateSyncGenesis(t *testing.T) { ) } -// Whenever we store a staker, a whole bunch a 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 +// Whenever we add or remove a staker, a number of on-disk data structures +// should be updated. +// +// This test verifies that the on-disk data structures are updated as expected. +func TestState_writeStakers(t *testing.T) { + const ( + primaryValidatorDuration = 28 * 24 * time.Hour + primaryDelegatorDuration = 14 * 24 * time.Hour + subnetValidatorDuration = 21 * 24 * time.Hour + + primaryValidatorReward = iota + primaryDelegatorReward + subnetValidatorReward + ) + 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()) + + subnetValidatorStartTime = primaryValidatorStartTime + subnetValidatorEndTime = subnetValidatorStartTime.Add(subnetValidatorDuration) + subnetValidatorEndTimeUnix = uint64(subnetValidatorEndTime.Unix()) + + primaryValidatorData = txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + End: primaryValidatorEndTimeUnix, + Wght: 1234, + } + primaryDelegatorData = txs.Validator{ + NodeID: primaryValidatorData.NodeID, + End: primaryDelegatorEndTimeUnix, + Wght: 6789, + } + subnetValidatorData = txs.Validator{ + NodeID: primaryValidatorData.NodeID, + End: subnetValidatorEndTimeUnix, + Wght: 9876, + } - // Check that the staker is duly stored/removed in P-chain state - checkStakerInState func(*require.Assertions, *state, *Staker) + subnetID = ids.GenerateTestID() + ) - // 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() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(endTime), - Wght: 1234, - } - validatorReward uint64 = 5678 - ) + primaryNetworkCurrentValidatorStaker, err := NewCurrentStaker( + addPrimaryNetworkValidator.ID(), + unsignedAddPrimaryNetworkValidator, + primaryValidatorStartTime, + primaryValidatorReward, + ) + require.NoError(t, err) - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) + unsignedAddPrimaryNetworkDelegator := createPermissionlessDelegatorTx(constants.PrimaryNetworkID, primaryDelegatorData) + addPrimaryNetworkDelegator := &txs.Tx{Unsigned: unsignedAddPrimaryNetworkDelegator} + require.NoError(t, addPrimaryNetworkDelegator.Initialize(txs.Codec)) - staker, err := NewCurrentStaker( - addPermValTx.ID(), - utx, - time.Unix(startTime, 0), - validatorReward, - ) - r.NoError(err) + primaryNetworkPendingDelegatorStaker, err := NewPendingStaker( + addPrimaryNetworkDelegator.ID(), + unsignedAddPrimaryNetworkDelegator, + ) + require.NoError(t, 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 - ) + unsignedAddSubnetValidator := createPermissionlessValidatorTx(t, subnetID, subnetValidatorData) + addSubnetValidator := &txs.Tx{Unsigned: unsignedAddSubnetValidator} + require.NoError(t, addSubnetValidator.Initialize(txs.Codec)) - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) + subnetCurrentValidatorStaker, err := NewCurrentStaker( + addSubnetValidator.ID(), + unsignedAddSubnetValidator, + subnetValidatorStartTime, + subnetValidatorReward, + ) + require.NoError(t, err) - val, err := NewCurrentStaker( - addPermValTx.ID(), - utxVal, - time.Unix(valStartTime, 0), - validatorReward, - ) - r.NoError(err) + tests := map[string]struct { + initialStakers []*Staker + initialTxs []*txs.Tx - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) + // Staker to insert or remove + staker *Staker + addStakerTx *txs.Tx // If tx is nil, the staker is being removed - del, err := NewCurrentStaker( - addPermDelTx.ID(), - utxDel, - time.Unix(delStartTime, 0), - delegatorReward, - ) - r.NoError(err) + // Check that the staker is duly stored/removed in P-chain state + expectedCurrentValidator *Staker + expectedPendingValidator *Staker + expectedCurrentDelegators []*Staker + expectedPendingDelegators []*Staker - r.NoError(s.PutCurrentValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + // Check that the validator entry has been set correctly in the + // in-memory validator set. + expectedValidatorSetOutput *validators.GetValidatorOutput - 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) + // Check whether weight/bls keys diffs are duly stored + expectedWeightDiff *ValidatorWeightDiff + expectedPublicKeyDiff maybe.Maybe[*bls.PublicKey] + }{ + "add current primary network validator": { + staker: primaryNetworkCurrentValidatorStaker, + addStakerTx: addPrimaryNetworkValidator, + expectedCurrentValidator: primaryNetworkCurrentValidatorStaker, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: primaryNetworkCurrentValidatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: primaryNetworkCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: false, + Amount: primaryNetworkCurrentValidatorStaker.Weight, }, - 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) + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](nil), + }, + "add current primary network delegator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkCurrentDelegatorStaker, + addStakerTx: 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, }, - 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) + }, + "add pending primary network validator": { + staker: primaryNetworkPendingValidatorStaker, + addStakerTx: addPrimaryNetworkValidator, + expectedPendingValidator: primaryNetworkPendingValidatorStaker, + }, + "add pending primary network delegator": { + initialStakers: []*Staker{primaryNetworkPendingValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkPendingDelegatorStaker, + addStakerTx: addPrimaryNetworkDelegator, + expectedPendingValidator: primaryNetworkPendingValidatorStaker, + expectedPendingDelegators: []*Staker{primaryNetworkPendingDelegatorStaker}, + }, + "add current subnet validator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: subnetCurrentValidatorStaker, + addStakerTx: addSubnetValidator, + expectedCurrentValidator: subnetCurrentValidatorStaker, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: subnetCurrentValidatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: subnetCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: false, + Amount: subnetCurrentValidatorStaker.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 + "delete current primary network validator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkCurrentValidatorStaker, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: true, + Amount: primaryNetworkCurrentValidatorStaker.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) + 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, }, - 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) + }, + "delete pending primary network validator": { + initialStakers: []*Staker{primaryNetworkPendingValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkPendingValidatorStaker, + }, + "delete pending primary network delegator": { + initialStakers: []*Staker{ + primaryNetworkPendingValidatorStaker, + primaryNetworkPendingDelegatorStaker, }, - 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) + initialTxs: []*txs.Tx{ + addPrimaryNetworkValidator, + addPrimaryNetworkDelegator, }, - 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) + staker: primaryNetworkPendingDelegatorStaker, + expectedPendingValidator: primaryNetworkPendingValidatorStaker, + }, + "delete current subnet validator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker, subnetCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator, addSubnetValidator}, + staker: subnetCurrentValidatorStaker, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: true, + Amount: subnetCurrentValidatorStaker.Weight, }, + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](primaryNetworkCurrentValidatorStaker.PublicKey), }, - "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, - } - ) + for name, test := range tests { + t.Run(name, func(t *testing.T) { + require := require.New(t) - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) + db := memdb.New() + state := newTestState(t, db) + + addOrDeleteStaker := func(staker *Staker, add bool) { + if add { + 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) + } + } else { + switch { + case staker.Priority.IsCurrentValidator(): + state.DeleteCurrentValidator(staker) + case staker.Priority.IsPendingValidator(): + state.DeletePendingValidator(staker) + case staker.Priority.IsCurrentDelegator(): + state.DeleteCurrentDelegator(staker) + case staker.Priority.IsPendingDelegator(): + state.DeletePendingDelegator(staker) + } + } + } - val, err := NewPendingStaker(addPermValTx.ID(), utxVal) - r.NoError(err) + // create and store the initial stakers + for _, staker := range test.initialStakers { + addOrDeleteStaker(staker, true) + } + for _, tx := range test.initialTxs { + state.AddTx(tx, status.Committed) + } - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) + state.SetHeight(0) + require.NoError(state.Commit()) - del, err := NewPendingStaker(addPermDelTx.ID(), utxDel) - r.NoError(err) + // create and store the staker under test + addOrDeleteStaker(test.staker, test.addStakerTx != nil) + if test.addStakerTx != nil { + state.AddTx(test.addStakerTx, status.Committed) + } - r.NoError(s.PutPendingValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + state.SetHeight(1) + require.NoError(state.Commit()) - s.PutPendingDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + // 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) - 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() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(endTime), - Wght: 1234, + if test.staker.SubnetID == constants.PrimaryNetworkID { + // Uptimes are only considered for primary network validators + _, _, err := state.GetUptime(test.staker.NodeID) + require.ErrorIs(err, database.ErrNotFound) } - validatorReward uint64 = 5678 - ) - - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - 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()) + } else { + require.NoError(err) + require.Equal(test.expectedCurrentValidator, currentValidator) + + if test.staker.SubnetID == constants.PrimaryNetworkID { + // Uptimes are only considered for primary network validators + upDuration, lastUpdated, err := state.GetUptime(currentValidator.NodeID) + require.NoError(err) + require.Zero(upDuration) + require.Equal(currentValidator.StartTime, lastUpdated) + } + } - 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 + it, err := state.GetCurrentDelegatorIterator(test.staker.SubnetID, test.staker.NodeID) + require.NoError(err) + require.Equal( + test.expectedCurrentDelegators, + iterator.ToSlice(it), ) - 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, + it, err = state.GetPendingDelegatorIterator(test.staker.SubnetID, test.staker.NodeID) + require.NoError(err) + require.Equal( + test.expectedPendingDelegators, + iterator.ToSlice(it), ) - 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, + require.Equal( + test.expectedValidatorSetOutput, + state.validators.GetMap(test.staker.SubnetID)[test.staker.NodeID], ) - 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, - } - ) - - 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()) - - s.DeletePendingValidator(staker) - r.NoError(s.Commit()) + 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) - 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) + weightDiff, err := unmarshalWeightDiff(weightDiffBytes) + require.NoError(err) + require.Equal(test.expectedWeightDiff, weightDiff) + } - _, 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, - } + publicKeyDiffBytes, err := state.validatorPublicKeyDiffsDB.Get(diffKey) + if test.expectedPublicKeyDiff.IsNothing() { + require.ErrorIs(err, database.ErrNotFound) + } else { + require.NoError(err) - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - Start: uint64(delStartTime), - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, + expectedPublicKeyDiff := test.expectedPublicKeyDiff.Value() + if expectedPublicKeyDiff != nil { + require.Equal(expectedPublicKeyDiff, bls.PublicKeyFromValidUncompressedBytes(publicKeyDiffBytes)) + } else { + require.Empty(publicKeyDiffBytes) } - ) - - 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) - - // 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 - rebuiltState := newTestState(t, db) + } - // 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 for the second iteration + 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) } @@ -988,43 +756,49 @@ func TestValidatorWeightDiff(t *testing.T) { } } -// Tests PutCurrentValidator, DeleteCurrentValidator, GetCurrentValidator, -// ApplyValidatorWeightDiffs, ApplyValidatorPublicKeyDiffs -func TestStateAddRemoveValidator(t *testing.T) { +func TestState_ApplyValidatorDiffs(t *testing.T) { require := require.New(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 @@ -1037,101 +811,172 @@ 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, + 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, + 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, + Weight: subnetStakers[2].Weight, + }, + subnetStakers[3].NodeID: { + NodeID: subnetStakers[3].NodeID, + Weight: subnetStakers[3].Weight, }, - stakers[2].NodeID: { - NodeID: stakers[2].NodeID, - Weight: stakers[2].Weight, + subnetStakers[4].NodeID: { + NodeID: subnetStakers[4].NodeID, + 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 { - for _, added := range diff.addedValidators { - added := added - require.NoError(state.PutCurrentValidator(&added)) - } - for _, added := range diff.addedDelegators { - added := added - state.PutCurrentDelegator(&added) + d, err := NewDiffOn(state) + require.NoError(err) + + type subnetIDNodeID struct { + subnetID ids.ID + nodeID ids.NodeID } - for _, removed := range diff.removedDelegators { - removed := removed - state.DeleteCurrentDelegator(&removed) + var expectedValidators set.Set[subnetIDNodeID] + for _, added := range diff.addedValidators { + 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()) + // Verify that the current state is as expected. 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) @@ -1142,37 +987,47 @@ func TestStateAddRemoveValidator(t *testing.T) { require.ErrorIs(err, database.ErrNotFound) } + primaryValidatorSet := state.validators.GetMap(constants.PrimaryNetworkID) + delete(primaryValidatorSet, defaultValidatorNodeID) // Ignore the genesis validator + require.Equal(diff.expectedPrimaryValidatorSet, primaryValidatorSet) + + require.Equal(diff.expectedSubnetValidatorSet, state.validators.GetMap(subnetID)) + + // Verify that applying diffs against the current state results in the + // expected state. for i := 0; i < currentIndex; i++ { prevDiff := diffs[i] prevHeight := uint64(i + 1) - primaryValidatorSet := copyValidatorSet(diff.expectedPrimaryValidatorSet) - require.NoError(state.ApplyValidatorWeightDiffs( - context.Background(), - primaryValidatorSet, - currentHeight, - prevHeight+1, - constants.PrimaryNetworkID, - )) - requireEqualWeightsValidatorSet(require, prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) - - require.NoError(state.ApplyValidatorPublicKeyDiffs( - context.Background(), - primaryValidatorSet, - currentHeight, - prevHeight+1, - )) - requireEqualPublicKeysValidatorSet(require, prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) - - subnetValidatorSet := copyValidatorSet(diff.expectedSubnetValidatorSet) - require.NoError(state.ApplyValidatorWeightDiffs( - context.Background(), - subnetValidatorSet, - currentHeight, - prevHeight+1, - subnetID, - )) - requireEqualWeightsValidatorSet(require, prevDiff.expectedSubnetValidatorSet, subnetValidatorSet) + { + primaryValidatorSet := copyValidatorSet(diff.expectedPrimaryValidatorSet) + require.NoError(state.ApplyValidatorWeightDiffs( + context.Background(), + primaryValidatorSet, + currentHeight, + prevHeight+1, + constants.PrimaryNetworkID, + )) + require.NoError(state.ApplyValidatorPublicKeyDiffs( + context.Background(), + primaryValidatorSet, + currentHeight, + prevHeight+1, + )) + require.Equal(prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) + } + + { + subnetValidatorSet := copyValidatorSet(diff.expectedSubnetValidatorSet) + require.NoError(state.ApplyValidatorWeightDiffs( + context.Background(), + subnetValidatorSet, + currentHeight, + prevHeight+1, + subnetID, + )) + require.Equal(prevDiff.expectedSubnetValidatorSet, subnetValidatorSet) + } } } } @@ -1188,36 +1043,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) From 91a7cd7f30798db77b7c7049d9fe662b81914e7c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 24 Oct 2024 10:43:46 -0400 Subject: [PATCH 215/400] fix test --- vms/platformvm/state/state_test.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index e9096af13a1c..522e5a912401 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -278,15 +278,13 @@ func TestState_writeStakers(t *testing.T) { addStakerTx: addSubnetValidator, expectedCurrentValidator: subnetCurrentValidatorStaker, expectedValidatorSetOutput: &validators.GetValidatorOutput{ - NodeID: subnetCurrentValidatorStaker.NodeID, - PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, - Weight: subnetCurrentValidatorStaker.Weight, + NodeID: subnetCurrentValidatorStaker.NodeID, + Weight: subnetCurrentValidatorStaker.Weight, }, expectedWeightDiff: &ValidatorWeightDiff{ Decrease: false, Amount: subnetCurrentValidatorStaker.Weight, }, - expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](nil), }, "delete current primary network validator": { initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, @@ -344,7 +342,6 @@ func TestState_writeStakers(t *testing.T) { Decrease: true, Amount: subnetCurrentValidatorStaker.Weight, }, - expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](primaryNetworkCurrentValidatorStaker.PublicKey), }, } From c848feef30038c116ad8e7389c54abf92c8d69d5 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 24 Oct 2024 10:44:54 -0400 Subject: [PATCH 216/400] fix test --- vms/platformvm/state/state_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index c6f6c0d52ec7..f3c952ff90d8 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -278,13 +278,15 @@ func TestState_writeStakers(t *testing.T) { addStakerTx: addSubnetValidator, expectedCurrentValidator: subnetCurrentValidatorStaker, expectedValidatorSetOutput: &validators.GetValidatorOutput{ - NodeID: subnetCurrentValidatorStaker.NodeID, - Weight: subnetCurrentValidatorStaker.Weight, + NodeID: subnetCurrentValidatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: subnetCurrentValidatorStaker.Weight, }, expectedWeightDiff: &ValidatorWeightDiff{ Decrease: false, Amount: subnetCurrentValidatorStaker.Weight, }, + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](nil), }, "delete current primary network validator": { initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, @@ -342,6 +344,7 @@ func TestState_writeStakers(t *testing.T) { Decrease: true, Amount: subnetCurrentValidatorStaker.Weight, }, + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](primaryNetworkCurrentValidatorStaker.PublicKey), }, } From 79c9b40074f82ae33ebf0e4ac8ac2191c3e81144 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 24 Oct 2024 10:49:15 -0400 Subject: [PATCH 217/400] nit --- vms/platformvm/state/state.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index cfb5888ecfa9..c70547265cf1 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2079,12 +2079,11 @@ func (s *state) writeCurrentStakers(updateValidators bool, height uint64, codecV } // TODO: Move validator set management out of the state package - // - // Attempt to update the stake metrics if !updateValidators { return nil } + // Update the stake metrics totalWeight, err := s.validators.TotalWeight(constants.PrimaryNetworkID) if err != nil { return fmt.Errorf("failed to get total weight of primary network: %w", err) From ed2fcfd4d058ba134ee4a22a55c40fbe56c6b972 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 24 Oct 2024 11:16:03 -0400 Subject: [PATCH 218/400] simplify test --- tests/e2e/p/permissionless_layer_one.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go index 44cb13b2445f..de4aaa11a080 100644 --- a/tests/e2e/p/permissionless_layer_one.go +++ b/tests/e2e/p/permissionless_layer_one.go @@ -248,19 +248,9 @@ var _ = e2e.DescribePChain("[Permissionless L1]", func() { }) advanceProposerVMPChainHeight := func() { - // We first must wait at least [RecentlyAcceptedWindowTTL] to ensure - // the next block will evict the prior block from the windower. + // We must wait at least [RecentlyAcceptedWindowTTL] to ensure the + // next block will reference the last accepted P-chain height. 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) From 08c4626facb6ad45311f60fa23b3f03d311afcad Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 24 Oct 2024 11:29:56 -0400 Subject: [PATCH 219/400] fix merge --- vms/platformvm/network/warp.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/network/warp.go b/vms/platformvm/network/warp.go index c774c37e2bde..911e407263f3 100644 --- a/vms/platformvm/network/warp.go +++ b/vms/platformvm/network/warp.go @@ -109,7 +109,7 @@ func (s signatureRequestVerifier) verifySubnetConversion( s.stateLock.Lock() defer s.stateLock.Unlock() - conversionID, _, _, err := s.state.GetSubnetConversion(subnetID) + conversion, err := s.state.GetSubnetConversion(subnetID) if err == database.ErrNotFound { return &common.AppError{ Code: ErrConversionDoesNotExist, @@ -123,10 +123,10 @@ func (s signatureRequestVerifier) verifySubnetConversion( } } - if msg.ID != conversionID { + if msg.ID != conversion.ConversionID { return &common.AppError{ Code: ErrMismatchedConversionID, - Message: fmt.Sprintf("provided conversionID %q != expected conversionID %q", msg.ID, conversionID), + Message: fmt.Sprintf("provided conversionID %q != expected conversionID %q", msg.ID, conversion.ConversionID), } } From e8b21ec88d26f3bc2d2cf3899470cf4b65e2fc44 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 24 Oct 2024 17:10:34 -0400 Subject: [PATCH 220/400] Verify no SoV + legacy overlap --- vms/platformvm/state/diff_test.go | 15 +++++++++++++++ vms/platformvm/state/subnet_only_validator.go | 17 ++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index 3e1ca3b2cdce..9cc67ff8d9ef 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -315,6 +315,15 @@ func TestDiffSubnetOnlyValidatorsErrors(t *testing.T) { }, expectedErr: ErrMutatedSubnetOnlyValidator, }, + { + name: "conflicting legacy subnetID and nodeID pair", + initialEndAccumulatedFee: 1, + sov: SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + NodeID: defaultValidatorNodeID, + }, + expectedErr: ErrConflictingSubnetOnlyValidator, + }, { name: "duplicate active subnetID and nodeID pair", initialEndAccumulatedFee: 1, @@ -341,6 +350,12 @@ func TestDiffSubnetOnlyValidatorsErrors(t *testing.T) { state := newTestState(t, memdb.New()) + require.NoError(state.PutCurrentValidator(&Staker{ + TxID: ids.GenerateTestID(), + SubnetID: sov.SubnetID, + NodeID: defaultValidatorNodeID, + })) + sov.EndAccumulatedFee = test.initialEndAccumulatedFee require.NoError(state.PutSubnetOnlyValidator(sov)) diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 978cb78c2727..9c58a60bf2e2 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -26,8 +26,9 @@ var ( _ btree.LessFunc[SubnetOnlyValidator] = SubnetOnlyValidator.Less _ utils.Sortable[SubnetOnlyValidator] = SubnetOnlyValidator{} - ErrMutatedSubnetOnlyValidator = errors.New("subnet only validator contains mutated constant fields") - ErrDuplicateSubnetOnlyValidator = errors.New("subnet only validator contains conflicting subnetID + nodeID pair") + ErrMutatedSubnetOnlyValidator = errors.New("subnet only validator contains mutated constant fields") + ErrConflictingSubnetOnlyValidator = errors.New("subnet only validator contains conflicting subnetID + nodeID pair") + ErrDuplicateSubnetOnlyValidator = errors.New("subnet only validator contains duplicate subnetID + nodeID pair") errUnexpectedSubnetIDNodeIDLength = fmt.Errorf("expected subnetID+nodeID entry length %d", subnetIDNodeIDEntryLength) ) @@ -233,7 +234,7 @@ func (d *subnetOnlyValidatorsDiff) hasSubnetOnlyValidator(subnetID ids.ID, nodeI return has, modified } -func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state SubnetOnlyValidators, sov SubnetOnlyValidator) error { +func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state Chain, sov SubnetOnlyValidator) error { var ( prevWeight uint64 prevActive bool @@ -248,6 +249,16 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state SubnetOnlyValida prevWeight = priorSOV.Weight prevActive = priorSOV.EndAccumulatedFee != 0 case database.ErrNotFound: + // Verify that there is not a legacy subnet validator with the same + // subnetID+nodeID as this L1 validator. + _, err := state.GetCurrentValidator(sov.SubnetID, sov.NodeID) + if err == nil { + return ErrConflictingSubnetOnlyValidator + } + if err != database.ErrNotFound { + return err + } + has, err := state.HasSubnetOnlyValidator(sov.SubnetID, sov.NodeID) if err != nil { return err From 7f517c7ced96bbcaf7a929939acf3eebb5f2b644 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 24 Oct 2024 18:40:16 -0400 Subject: [PATCH 221/400] Fix legacy validator migration --- vms/platformvm/state/state.go | 627 +++++++++++++++-------------- vms/platformvm/state/state_test.go | 41 ++ 2 files changed, 359 insertions(+), 309 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 948dede49f1e..448f7414d78c 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1952,10 +1952,11 @@ func (s *state) write(updateValidators bool, height uint64) error { return errors.Join( s.writeBlocks(), s.writeExpiry(), - s.writeSubnetOnlyValidators(updateValidators, height), - s.writeCurrentStakers(updateValidators, height, codecVersion), + s.writeValidatorDiffs(height), + s.writeCurrentStakers(updateValidators, codecVersion), s.writePendingStakers(), s.WriteValidatorMetadata(s.currentValidatorList, s.currentSubnetValidatorList, codecVersion), // Must be called after writeCurrentStakers + s.writeSubnetOnlyValidators(updateValidators), s.writeTXs(), s.writeRewardUTXOs(), s.writeUTXOs(), @@ -2206,248 +2207,78 @@ func (s *state) writeExpiry() error { return nil } -// TODO: Add caching -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 { - return err - } - } - maps.Clear(s.sovDiff.modifiedTotalWeight) - - historicalDiffs, err := s.makeSubnetOnlyValidatorHistoricalDiffs() - if err != nil { - return err +func (s *state) getInheritedPublicKey(nodeID ids.NodeID) (*bls.PublicKey, error) { + if vdr, ok := s.currentStakers.validators[constants.PrimaryNetworkID][nodeID]; ok && vdr.validator != nil { + // The primary network validator is still present. + return vdr.validator.PublicKey, nil } - 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 - } - } + if vdr, ok := s.currentStakers.validatorDiffs[constants.PrimaryNetworkID][nodeID]; ok && vdr.validator != nil { + // The primary network validator is being removed. + return vdr.validator.PublicKey, nil } - sovChanges := s.sovDiff.modified - // Perform deletions: - 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 - // the validator was added and then immediately removed. - continue - } - if err != nil { - return err - } - - subnetIDNodeID := subnetIDNodeID{ - subnetID: sov.SubnetID, - nodeID: sov.NodeID, - } - subnetIDNodeIDKey := subnetIDNodeID.Marshal() - if err := s.subnetIDNodeIDDB.Delete(subnetIDNodeIDKey); err != nil { - return err - } - - if priorSOV.isActive() { - delete(s.activeSOVLookup, validationID) - s.activeSOVs.Delete(priorSOV) - err = deleteSubnetOnlyValidator(s.activeDB, validationID) - } else { - err = deleteSubnetOnlyValidator(s.inactiveDB, validationID) - } - if err != nil { - return err - } - - // TODO: Move the validator set management out of the state package - if !updateValidators { - continue - } + // This should never happen as the primary network diffs are + // written last and subnet validator times must be a subset + // of the primary network validator times. + return nil, fmt.Errorf("%w: %s", errMissingPrimaryNetworkValidator, nodeID) +} - 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) - } +func (s *state) writeValidatorDiffs(height uint64) error { + type validatorChanges struct { + weightDiff ValidatorWeightDiff + prevPublicKey []byte + newPublicKey []byte } + changes := make(map[subnetIDNodeID]*validatorChanges, len(s.sovDiff.modified)) - // 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 - } - - if priorSOV.isActive() { - delete(s.activeSOVLookup, validationID) - s.activeSOVs.Delete(priorSOV) - err = deleteSubnetOnlyValidator(s.activeDB, validationID) - } else { - err = deleteSubnetOnlyValidator(s.inactiveDB, 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 - } - - // 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 + for subnetID, subnetDiffs := range s.currentStakers.validatorDiffs { + for nodeID, diff := range subnetDiffs { + change := &validatorChanges{ + weightDiff: ValidatorWeightDiff{ + Decrease: diff.validatorStatus == deleted, + }, } - 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 diff.validatorStatus != unmodified { + change.weightDiff.Amount = diff.validator.Weight } - } - if err != nil { - return err - } - } - - // Perform additions: - for validationID, sov := range sovChanges { - validationID := validationID - - subnetIDNodeID := subnetIDNodeID{ - subnetID: sov.SubnetID, - nodeID: sov.NodeID, - } - subnetIDNodeIDKey := subnetIDNodeID.Marshal() - if err := s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, validationID[:]); 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 - } + for _, staker := range diff.deletedDelegators { + if err := change.weightDiff.Add(true, staker.Weight); err != nil { + return fmt.Errorf("failed to decrease node weight diff: %w", err) + } + } - // TODO: Move the validator set management out of the state package - if !updateValidators { - continue - } + addedDelegatorIterator := iterator.FromTree(diff.addedDelegators) + for addedDelegatorIterator.Next() { + staker := addedDelegatorIterator.Value() + if err := change.weightDiff.Add(false, staker.Weight); err != nil { + addedDelegatorIterator.Release() + return fmt.Errorf("failed to increase node weight diff: %w", err) + } + } + addedDelegatorIterator.Release() - 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) + pk, err := s.getInheritedPublicKey(nodeID) + if err != nil { + return err + } + if pk != nil { + switch diff.validatorStatus { + case added: + change.newPublicKey = bls.PublicKeyToUncompressedBytes(pk) + case deleted: + change.prevPublicKey = bls.PublicKeyToUncompressedBytes(pk) + } } - 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 + subnetIDNodeID := subnetIDNodeID{ + subnetID: subnetID, + nodeID: nodeID, + } + changes[subnetIDNodeID] = change } } - s.sovDiff = newSubnetOnlyValidatorsDiff() - 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) @@ -2455,7 +2286,7 @@ func (s *state) makeSubnetOnlyValidatorHistoricalDiffs() (map[subnetIDNodeID]*va continue } if err != nil { - return nil, err + return err } var ( @@ -2474,7 +2305,7 @@ func (s *state) makeSubnetOnlyValidatorHistoricalDiffs() (map[subnetIDNodeID]*va } if err := diff.weightDiff.Add(true, priorSOV.Weight); err != nil { - return nil, err + return err } } @@ -2501,14 +2332,45 @@ func (s *state) makeSubnetOnlyValidatorHistoricalDiffs() (map[subnetIDNodeID]*va } if err := diff.weightDiff.Add(false, sov.Weight); err != nil { - return nil, err + return err + } + } + + for subnetIDNodeID, diff := range changes { + 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 + } } } + return nil +} + +func getOrDefault[K comparable, V any](m map[K]*V, k K) *V { + if v, ok := m[k]; ok { + return v + } - return changes, nil + v := new(V) + m[k] = v + return v } -func (s *state) writeCurrentStakers(updateValidators bool, height uint64, codecVersion uint16) error { +func (s *state) writeCurrentStakers(updateValidators bool, codecVersion uint16) error { for subnetID, validatorDiffs := range s.currentStakers.validatorDiffs { // We must write the primary network stakers last because writing subnet // validator diffs may depend on the primary network validator diffs to @@ -2517,33 +2379,31 @@ func (s *state) writeCurrentStakers(updateValidators bool, height uint64, codecV continue } - delete(s.currentStakers.validatorDiffs, subnetID) - err := s.writeCurrentStakersSubnetDiff( subnetID, validatorDiffs, updateValidators, - height, codecVersion, ) if err != nil { return err } + + delete(s.currentStakers.validatorDiffs, subnetID) } if validatorDiffs, ok := s.currentStakers.validatorDiffs[constants.PrimaryNetworkID]; ok { - delete(s.currentStakers.validatorDiffs, constants.PrimaryNetworkID) - err := s.writeCurrentStakersSubnetDiff( constants.PrimaryNetworkID, validatorDiffs, updateValidators, - height, codecVersion, ) if err != nil { return err } + + delete(s.currentStakers.validatorDiffs, constants.PrimaryNetworkID) } // TODO: Move validator set management out of the state package @@ -2566,7 +2426,6 @@ func (s *state) writeCurrentStakersSubnetDiff( subnetID ids.ID, validatorDiffs map[ids.NodeID]*diffValidator, updateValidators bool, - height uint64, codecVersion uint16, ) error { // Select db to write to @@ -2579,52 +2438,13 @@ func (s *state) writeCurrentStakersSubnetDiff( // Record the change in weight and/or public key for each validator. for nodeID, validatorDiff := range validatorDiffs { - 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 as the primary network diffs are - // written last and subnet validator times must be a subset - // of the primary network validator times. - return fmt.Errorf("%w: %s", errMissingPrimaryNetworkValidator, nodeID) - } - } - - weightDiff.Amount = staker.Weight - } - + weightDiff := &ValidatorWeightDiff{} 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 - } - } + staker := validatorDiff.validator + + weightDiff.Decrease = false + weightDiff.Amount = staker.Weight // The validator is being added. // @@ -2653,23 +2473,10 @@ func (s *state) writeCurrentStakersSubnetDiff( 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 - } - } + weightDiff.Decrease = true + weightDiff.Amount = validatorDiff.validator.Weight - if err := validatorDB.Delete(staker.TxID[:]); err != nil { + if err := validatorDB.Delete(validatorDiff.validator.TxID[:]); err != nil { return fmt.Errorf("failed to delete current staker: %w", err) } @@ -2691,14 +2498,6 @@ func (s *state) writeCurrentStakersSubnetDiff( continue } - 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 @@ -2708,11 +2507,17 @@ func (s *state) writeCurrentStakersSubnetDiff( err = s.validators.RemoveWeight(subnetID, nodeID, weightDiff.Amount) } else { if validatorDiff.validatorStatus == added { + var pk *bls.PublicKey + pk, err = s.getInheritedPublicKey(nodeID) + if err != nil { + return err + } + err = s.validators.AddStaker( subnetID, nodeID, pk, - staker.TxID, + validatorDiff.validator.TxID, weightDiff.Amount, ) } else { @@ -2824,6 +2629,210 @@ func writePendingDiff( return nil } +// TODO: Add caching +// +// writeSubnetOnlyValidators must be called after writeCurrentStakers to ensure +// any legacy validators that were removed and then re-added as SoVs are +// correctly written +func (s *state) writeSubnetOnlyValidators(updateValidators bool) error { + // 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.modifiedTotalWeight) + + sovChanges := s.sovDiff.modified + // Perform deletions: + 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 + // the validator was added and then immediately removed. + continue + } + if err != nil { + return err + } + + subnetIDNodeID := subnetIDNodeID{ + subnetID: sov.SubnetID, + nodeID: sov.NodeID, + } + subnetIDNodeIDKey := subnetIDNodeID.Marshal() + if err := s.subnetIDNodeIDDB.Delete(subnetIDNodeIDKey); err != nil { + return err + } + + if priorSOV.isActive() { + delete(s.activeSOVLookup, validationID) + s.activeSOVs.Delete(priorSOV) + err = deleteSubnetOnlyValidator(s.activeDB, validationID) + } else { + err = deleteSubnetOnlyValidator(s.inactiveDB, validationID) + } + if err != nil { + return err + } + + // TODO: Move the validator set management out of the state package + if !updateValidators { + continue + } + + 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 + } + + if priorSOV.isActive() { + delete(s.activeSOVLookup, validationID) + s.activeSOVs.Delete(priorSOV) + err = deleteSubnetOnlyValidator(s.activeDB, validationID) + } else { + err = deleteSubnetOnlyValidator(s.inactiveDB, 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 + } + + // 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 { + validationID := validationID + + subnetIDNodeID := subnetIDNodeID{ + subnetID: sov.SubnetID, + nodeID: sov.NodeID, + } + subnetIDNodeIDKey := subnetIDNodeID.Marshal() + if err := s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, validationID[:]); err != nil { + return err + } + + var ( + isActive = sov.isActive() + err error + ) + 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() + return nil +} + func (s *state) writeTXs() error { for txID, txStatus := range s.addedTxs { txID := txID diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index b5ebc572fea8..f4f50ebdf05d 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1889,3 +1889,44 @@ func TestSubnetOnlyValidators(t *testing.T) { }) } } + +func TestSubnetOnlyValidatorAfterLegacyRemoval(t *testing.T) { + require := require.New(t) + + db := memdb.New() + state := newTestState(t, db) + + legacyStaker := &Staker{ + TxID: ids.GenerateTestID(), + NodeID: defaultValidatorNodeID, + PublicKey: nil, + SubnetID: ids.GenerateTestID(), + Weight: 1, + StartTime: genesistest.DefaultValidatorStartTime, + EndTime: genesistest.DefaultValidatorEndTime, + PotentialReward: 0, + } + require.NoError(state.PutCurrentValidator(legacyStaker)) + + state.SetHeight(1) + require.NoError(state.Commit()) + + state.DeleteCurrentValidator(legacyStaker) + + sov := SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: legacyStaker.SubnetID, + NodeID: legacyStaker.NodeID, + PublicKey: utils.RandomBytes(bls.PublicKeyLen), + RemainingBalanceOwner: utils.RandomBytes(32), + DeactivationOwner: utils.RandomBytes(32), + StartTime: 1, + Weight: 2, + MinNonce: 3, + EndAccumulatedFee: 4, + } + require.NoError(state.PutSubnetOnlyValidator(sov)) + + state.SetHeight(2) + require.NoError(state.Commit()) +} From 08bd9e3ae82cb0f8e4008d38d5d7ecbff232dbe4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 25 Oct 2024 16:31:17 -0400 Subject: [PATCH 222/400] ACP-77: Add subnetIDNodeID struct --- vms/platformvm/state/state_test.go | 4 -- vms/platformvm/state/subnet_id_node_id.go | 37 +++++++++++ .../state/subnet_id_node_id_test.go | 66 +++++++++++++++++++ 3 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 vms/platformvm/state/subnet_id_node_id.go create mode 100644 vms/platformvm/state/subnet_id_node_id_test.go diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index b965af1531eb..85df176ce42a 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -944,10 +944,6 @@ func TestState_ApplyValidatorDiffs(t *testing.T) { d, err := NewDiffOn(state) require.NoError(err) - type subnetIDNodeID struct { - subnetID ids.ID - nodeID ids.NodeID - } var expectedValidators set.Set[subnetIDNodeID] for _, added := range diff.addedValidators { require.NoError(d.PutCurrentValidator(&added)) diff --git a/vms/platformvm/state/subnet_id_node_id.go b/vms/platformvm/state/subnet_id_node_id.go new file mode 100644 index 000000000000..208c1cf8f447 --- /dev/null +++ b/vms/platformvm/state/subnet_id_node_id.go @@ -0,0 +1,37 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package state + +import ( + "fmt" + + "github.com/ava-labs/avalanchego/ids" +) + +// subnetIDNodeID = [subnetID] + [nodeID] +const subnetIDNodeIDEntryLength = ids.IDLen + ids.NodeIDLen + +var errUnexpectedSubnetIDNodeIDLength = fmt.Errorf("expected subnetID+nodeID entry length %d", subnetIDNodeIDEntryLength) + +type subnetIDNodeID struct { + subnetID ids.ID + 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 +} diff --git a/vms/platformvm/state/subnet_id_node_id_test.go b/vms/platformvm/state/subnet_id_node_id_test.go new file mode 100644 index 000000000000..4ed720b95a8e --- /dev/null +++ b/vms/platformvm/state/subnet_id_node_id_test.go @@ -0,0 +1,66 @@ +// 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 FuzzSubnetIDNodeIDMarshal(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + require := require.New(t) + + var v subnetIDNodeID + fz := fuzzer.NewFuzzer(data) + fz.Fill(&v) + + marshalledData := v.Marshal() + + var parsed subnetIDNodeID + require.NoError(parsed.Unmarshal(marshalledData)) + require.Equal(v, parsed) + }) +} + +func FuzzSubnetIDNodeIDUnmarshal(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + require := require.New(t) + + var v subnetIDNodeID + if err := v.Unmarshal(data); err != nil { + require.ErrorIs(err, errUnexpectedSubnetIDNodeIDLength) + return + } + + marshalledData := v.Marshal() + require.Equal(data, marshalledData) + }) +} + +func FuzzSubnetIDNodeIDMarshalOrdering(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ( + v0 subnetIDNodeID + v1 subnetIDNodeID + ) + fz := fuzzer.NewFuzzer(data) + fz.Fill(&v0, &v1) + + if v0.subnetID == v1.subnetID { + return + } + + key0 := v0.Marshal() + key1 := v1.Marshal() + require.Equal( + t, + v0.subnetID.Compare(v1.subnetID), + bytes.Compare(key0, key1), + ) + }) +} From 78c1c3de58bf69e26f2764ee5b3e840413a32997 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 26 Oct 2024 17:50:18 -0400 Subject: [PATCH 223/400] nit --- vms/platformvm/state/subnet_id_node_id_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/state/subnet_id_node_id_test.go b/vms/platformvm/state/subnet_id_node_id_test.go index 4ed720b95a8e..848893170e2b 100644 --- a/vms/platformvm/state/subnet_id_node_id_test.go +++ b/vms/platformvm/state/subnet_id_node_id_test.go @@ -42,7 +42,7 @@ func FuzzSubnetIDNodeIDUnmarshal(f *testing.F) { }) } -func FuzzSubnetIDNodeIDMarshalOrdering(f *testing.F) { +func FuzzSubnetIDNodeIDOrdering(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { var ( v0 subnetIDNodeID From d2137ef6d9a0b4de0d9badb9cf2e352fd75e54b9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 26 Oct 2024 18:16:45 -0400 Subject: [PATCH 224/400] fix merge --- vms/platformvm/state/subnet_only_validator.go | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 9c58a60bf2e2..6d2a0f6afb9e 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -19,9 +19,6 @@ import ( safemath "github.com/ava-labs/avalanchego/utils/math" ) -// subnetIDNodeID = [subnetID] + [nodeID] -const subnetIDNodeIDEntryLength = ids.IDLen + ids.NodeIDLen - var ( _ btree.LessFunc[SubnetOnlyValidator] = SubnetOnlyValidator.Less _ utils.Sortable[SubnetOnlyValidator] = SubnetOnlyValidator{} @@ -29,8 +26,6 @@ var ( ErrMutatedSubnetOnlyValidator = errors.New("subnet only validator contains mutated constant fields") ErrConflictingSubnetOnlyValidator = errors.New("subnet only validator contains conflicting subnetID + nodeID pair") ErrDuplicateSubnetOnlyValidator = errors.New("subnet only validator contains duplicate subnetID + nodeID pair") - - errUnexpectedSubnetIDNodeIDLength = fmt.Errorf("expected subnetID+nodeID entry length %d", subnetIDNodeIDEntryLength) ) type SubnetOnlyValidators interface { @@ -175,28 +170,6 @@ func deleteSubnetOnlyValidator(db database.KeyValueDeleter, validationID ids.ID) return db.Delete(validationID[:]) } -type subnetIDNodeID struct { - subnetID ids.ID - 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 5845d11eed4895c72890e43a5c5a50c8ac0d9c1d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 26 Oct 2024 19:36:20 -0400 Subject: [PATCH 225/400] Split writeCurrentStakers into multiple functions --- vms/platformvm/state/stakers.go | 29 +++ vms/platformvm/state/state.go | 362 ++++++++++++++++---------------- 2 files changed, 208 insertions(+), 183 deletions(-) diff --git a/vms/platformvm/state/stakers.go b/vms/platformvm/state/stakers.go index 658796855958..14e4dcf7b1ef 100644 --- a/vms/platformvm/state/stakers.go +++ b/vms/platformvm/state/stakers.go @@ -5,6 +5,7 @@ package state import ( "errors" + "fmt" "github.com/google/btree" @@ -273,6 +274,34 @@ type diffValidator struct { deletedDelegators map[ids.ID]*Staker } +func (d *diffValidator) WeightDiff() (ValidatorWeightDiff, error) { + weightDiff := ValidatorWeightDiff{ + Decrease: d.validatorStatus == deleted, + } + if d.validatorStatus != unmodified { + weightDiff.Amount = d.validator.Weight + } + + for _, staker := range d.deletedDelegators { + if err := weightDiff.Add(true, staker.Weight); err != nil { + return ValidatorWeightDiff{}, fmt.Errorf("failed to decrease node weight diff: %w", err) + } + } + + addedDelegatorIterator := iterator.FromTree(d.addedDelegators) + defer addedDelegatorIterator.Release() + + for addedDelegatorIterator.Next() { + staker := addedDelegatorIterator.Value() + + if err := weightDiff.Add(false, staker.Weight); err != nil { + return ValidatorWeightDiff{}, fmt.Errorf("failed to increase node weight diff: %w", err) + } + } + + return weightDiff, nil +} + // GetValidator attempts to fetch the validator with the given subnetID and // nodeID. // Invariant: Assumes that the validator will never be removed and then added. diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index c70547265cf1..33ae0e61e2ee 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -4,6 +4,7 @@ package state import ( + "bytes" "context" "errors" "fmt" @@ -14,6 +15,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" @@ -1792,7 +1794,9 @@ func (s *state) write(updateValidators bool, height uint64) error { return errors.Join( s.writeBlocks(), s.writeExpiry(), - s.writeCurrentStakers(updateValidators, height, codecVersion), + s.updateValidatorManager(updateValidators), + s.writeValidatorDiffs(height), + s.writeCurrentStakers(codecVersion), s.writePendingStakers(), s.WriteValidatorMetadata(s.currentValidatorList, s.currentSubnetValidatorList, codecVersion), // Must be called after writeCurrentStakers s.writeTXs(), @@ -2040,47 +2044,75 @@ func (s *state) writeExpiry() error { return nil } -func (s *state) writeCurrentStakers(updateValidators bool, height uint64, codecVersion uint16) error { +// getInheritedPublicKey returns the primary network validator's public key. +// +// Note: This function may return a nil public key and no error if the primary +// network validator does not have a public key. +func (s *state) getInheritedPublicKey(nodeID ids.NodeID) (*bls.PublicKey, error) { + if vdr, ok := s.currentStakers.validators[constants.PrimaryNetworkID][nodeID]; ok && vdr.validator != nil { + // The primary network validator is present. + return vdr.validator.PublicKey, nil + } + if vdr, ok := s.currentStakers.validatorDiffs[constants.PrimaryNetworkID][nodeID]; ok && vdr.validator != nil { + // The primary network validator is being removed. + return vdr.validator.PublicKey, nil + } + return nil, fmt.Errorf("%w: %s", errMissingPrimaryNetworkValidator, nodeID) +} + +// updateValidatorManager updates the validator manager with the pending +// validator set changes. +// +// This function must be called prior to writeCurrentStakers. +func (s *state) updateValidatorManager(updateValidators bool) error { + if !updateValidators { + return nil + } + for subnetID, validatorDiffs := range s.currentStakers.validatorDiffs { - // We must write the primary network stakers last because writing subnet - // validator diffs may depend on the primary network validator diffs to - // inherit the public keys. - if subnetID == constants.PrimaryNetworkID { - continue - } + // Record the change in weight and/or public key for each validator. + for nodeID, diff := range validatorDiffs { + weightDiff, err := diff.WeightDiff() + if err != nil { + return err + } - delete(s.currentStakers.validatorDiffs, subnetID) + if weightDiff.Amount == 0 { + continue // No weight change; go to the next validator. + } - err := s.writeCurrentStakersSubnetDiff( - subnetID, - validatorDiffs, - updateValidators, - height, - codecVersion, - ) - if err != nil { - return err - } - } + if weightDiff.Decrease { + if err := s.validators.RemoveWeight(subnetID, nodeID, weightDiff.Amount); err != nil { + return fmt.Errorf("failed to reduce validator weight: %w", err) + } + continue + } - if validatorDiffs, ok := s.currentStakers.validatorDiffs[constants.PrimaryNetworkID]; ok { - delete(s.currentStakers.validatorDiffs, constants.PrimaryNetworkID) + if diff.validatorStatus != added { + if err := s.validators.AddWeight(subnetID, nodeID, weightDiff.Amount); err != nil { + return fmt.Errorf("failed to increase validator weight: %w", err) + } + continue + } - err := s.writeCurrentStakersSubnetDiff( - constants.PrimaryNetworkID, - validatorDiffs, - updateValidators, - height, - codecVersion, - ) - if err != nil { - return err - } - } + pk, err := s.getInheritedPublicKey(nodeID) + if err != nil { + // This should never happen as there should always be a primary + // network validator corresponding to a subnet validator. + return err + } - // TODO: Move validator set management out of the state package - if !updateValidators { - return nil + err = s.validators.AddStaker( + subnetID, + nodeID, + pk, + diff.validator.TxID, + weightDiff.Amount, + ) + if err != nil { + return fmt.Errorf("failed to add validator: %w", err) + } + } } // Update the stake metrics @@ -2094,185 +2126,153 @@ func (s *state) writeCurrentStakers(updateValidators bool, height uint64, codecV return nil } -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 - } - - // Record the change in weight and/or public key for each validator. - for nodeID, validatorDiff := range validatorDiffs { - 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 as the primary network diffs are - // written last and subnet validator times must be a subset - // of the primary network validator times. - return fmt.Errorf("%w: %s", errMissingPrimaryNetworkValidator, nodeID) - } +// writeValidatorDiffs writes the validator set diff contained by the pending +// validator set changes to disk. +// +// This function must be called prior to writeCurrentStakers. +func (s *state) writeValidatorDiffs(height uint64) error { + type validatorChanges struct { + weightDiff ValidatorWeightDiff + prevPublicKey []byte + newPublicKey []byte + } + changes := make(map[subnetIDNodeID]*validatorChanges) + + // Calculate the changes to the pre-ACP-77 validator set + for subnetID, subnetDiffs := range s.currentStakers.validatorDiffs { + for nodeID, diff := range subnetDiffs { + weightDiff, err := diff.WeightDiff() + if err != nil { + return err } - weightDiff.Amount = staker.Weight - } + pk, err := s.getInheritedPublicKey(nodeID) + if err != nil { + // This should never happen as there should always be a primary + // network validator corresponding to a subnet validator. + return err + } - switch validatorDiff.validatorStatus { - case added: + change := &validatorChanges{ + weightDiff: weightDiff, + } 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 + switch diff.validatorStatus { + case added: + change.newPublicKey = bls.PublicKeyToUncompressedBytes(pk) + case deleted: + change.prevPublicKey = bls.PublicKeyToUncompressedBytes(pk) } } - // 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, + subnetIDNodeID := subnetIDNodeID{ + subnetID: subnetID, + nodeID: nodeID, } + changes[subnetIDNodeID] = change + } + } - metadataBytes, err := MetadataCodec.Marshal(codecVersion, metadata) + // Write the changes to the database + for subnetIDNodeID, diff := range changes { + diffKey := marshalDiffKey(subnetIDNodeID.subnetID, height, subnetIDNodeID.nodeID) + if diff.weightDiff.Amount != 0 { + err := s.validatorWeightDiffsDB.Put( + diffKey, + marshalWeightDiff(&diff.weightDiff), + ) if err != nil { - return fmt.Errorf("failed to serialize current validator: %w", err) + return err } - - if err = validatorDB.Put(staker.TxID[:], metadataBytes); err != nil { - return fmt.Errorf("failed to write current validator to list: %w", err) + } + if !bytes.Equal(diff.prevPublicKey, diff.newPublicKey) { + err := s.validatorPublicKeyDiffsDB.Put( + diffKey, + diff.prevPublicKey, + ) + if err != nil { + return err } + } + } + return nil +} - 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 - } - } +func (s *state) writeCurrentStakers(codecVersion uint16) error { + for subnetID, validatorDiffs := range s.currentStakers.validatorDiffs { + // 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 { + switch validatorDiff.validatorStatus { + case added: + staker := validatorDiff.validator - s.validatorState.DeleteValidatorMetadata(nodeID, subnetID) - } + // 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 := writeCurrentDelegatorDiff( - delegatorDB, - weightDiff, - validatorDiff, - codecVersion, - ) - if err != nil { - return err - } + metadataBytes, err := MetadataCodec.Marshal(codecVersion, metadata) + if err != nil { + return fmt.Errorf("failed to serialize current validator: %w", err) + } - if weightDiff.Amount == 0 { - // No weight change to record; go to next validator. - continue - } + if err = validatorDB.Put(staker.TxID[:], metadataBytes); err != nil { + return fmt.Errorf("failed to write current validator to list: %w", err) + } - err = s.validatorWeightDiffsDB.Put( - marshalDiffKey(subnetID, height, nodeID), - marshalWeightDiff(weightDiff), - ) - if err != nil { - return err - } + s.validatorState.LoadValidatorMetadata(nodeID, subnetID, metadata) + case deleted: + if err := validatorDB.Delete(validatorDiff.validator.TxID[:]); err != nil { + return fmt.Errorf("failed to delete current staker: %w", err) + } - // TODO: Move the validator set management out of the state package - if !updateValidators { - continue - } + s.validatorState.DeleteValidatorMetadata(nodeID, subnetID) + } - 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) + err := writeCurrentDelegatorDiff( + delegatorDB, + validatorDiff, + codecVersion, + ) + if err != nil { + return err } } - if err != nil { - return fmt.Errorf("failed to update validator weight: %w", err) - } } + maps.Clear(s.currentStakers.validatorDiffs) return nil } func writeCurrentDelegatorDiff( currentDelegatorList linkeddb.LinkedDB, - weightDiff *ValidatorWeightDiff, validatorDiff *diffValidator, codecVersion uint16, ) error { addedDelegatorIterator := iterator.FromTree(validatorDiff.addedDelegators) defer addedDelegatorIterator.Release() + for addedDelegatorIterator.Next() { staker := addedDelegatorIterator.Value() - if err := weightDiff.Add(false, staker.Weight); err != nil { - return fmt.Errorf("failed to increase node weight diff: %w", err) - } - metadata := &delegatorMetadata{ txID: staker.TxID, PotentialReward: staker.PotentialReward, @@ -2284,10 +2284,6 @@ func writeCurrentDelegatorDiff( } for _, staker := range validatorDiff.deletedDelegators { - if err := weightDiff.Add(true, staker.Weight); err != nil { - return fmt.Errorf("failed to decrease node weight diff: %w", err) - } - if err := currentDelegatorList.Delete(staker.TxID[:]); err != nil { return fmt.Errorf("failed to delete current staker: %w", err) } From d0d1602264c040c6202659454fc299f68d491f55 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 26 Oct 2024 20:09:45 -0400 Subject: [PATCH 226/400] reduce diff --- 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 d4353aafb9af..4e34a6bd1cba 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2418,7 +2418,7 @@ func (s *state) writeValidatorDiffs(height uint64) error { } changes := make(map[subnetIDNodeID]*validatorChanges, len(s.sovDiff.modified)) - // Perform pre-ACP-77 validator set changes: + // Calculate the changes to the pre-ACP-77 validator set for subnetID, subnetDiffs := range s.currentStakers.validatorDiffs { for nodeID, diff := range subnetDiffs { weightDiff, err := diff.WeightDiff() @@ -2428,6 +2428,8 @@ func (s *state) writeValidatorDiffs(height uint64) error { pk, err := s.getInheritedPublicKey(nodeID) if err != nil { + // This should never happen as there should always be a primary + // network validator corresponding to a subnet validator. return err } From d397375ea8a4cc8b5312cae64c48cdadecaacddf Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 26 Oct 2024 20:20:08 -0400 Subject: [PATCH 227/400] reduce diff --- vms/platformvm/state/state.go | 85 ++++++++++++----------------------- 1 file changed, 29 insertions(+), 56 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 4e34a6bd1cba..841fe972278b 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2720,49 +2720,18 @@ func (s *state) writeSubnetOnlyValidators() error { // 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 - // the validator was added and then immediately removed. - continue - } - if err != nil { - return err - } - subnetIDNodeID := subnetIDNodeID{ subnetID: sov.SubnetID, nodeID: sov.NodeID, } subnetIDNodeIDKey := subnetIDNodeID.Marshal() - if err := s.subnetIDNodeIDDB.Delete(subnetIDNodeIDKey); err != nil { - return err - } - - if priorSOV.isActive() { - delete(s.activeSOVLookup, validationID) - s.activeSOVs.Delete(priorSOV) - err = deleteSubnetOnlyValidator(s.activeDB, validationID) - } else { - err = deleteSubnetOnlyValidator(s.inactiveDB, validationID) - } - if err != nil { - return 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 - } + err := s.subnetIDNodeIDDB.Delete(subnetIDNodeIDKey) if err != nil { return err } - if priorSOV.isActive() { + priorSOV, wasActive := s.activeSOVLookup[validationID] + if wasActive { delete(s.activeSOVLookup, validationID) s.activeSOVs.Delete(priorSOV) err = deleteSubnetOnlyValidator(s.activeDB, validationID) @@ -2772,34 +2741,38 @@ func (s *state) writeSubnetOnlyValidators() error { 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 - } - - // The next loop shouldn't consider this change. - delete(sovChanges, validationID) } - // Perform additions: + // Perform modifications and additions: for validationID, sov := range sovChanges { - subnetIDNodeID := subnetIDNodeID{ - subnetID: sov.SubnetID, - nodeID: sov.NodeID, - } - subnetIDNodeIDKey := subnetIDNodeID.Marshal() - if err := s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, validationID[:]); err != nil { + priorSOV, err := s.getPersistedSubnetOnlyValidator(validationID) + switch err { + case nil: + // This is modifying an existing validator + if priorSOV.isActive() { + delete(s.activeSOVLookup, validationID) + s.activeSOVs.Delete(priorSOV) + err = deleteSubnetOnlyValidator(s.activeDB, validationID) + } else { + err = deleteSubnetOnlyValidator(s.inactiveDB, validationID) + } + if err != nil { + return err + } + case database.ErrNotFound: + // This is a new validator + subnetIDNodeID := subnetIDNodeID{ + subnetID: sov.SubnetID, + nodeID: sov.NodeID, + } + subnetIDNodeIDKey := subnetIDNodeID.Marshal() + if err := s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, validationID[:]); err != nil { + return err + } + default: return err } - var err error if sov.isActive() { s.activeSOVLookup[validationID] = sov s.activeSOVs.ReplaceOrInsert(sov) From 5f8a09ca115f8b363e1d5a9d9506e3150c10f270 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 26 Oct 2024 20:25:42 -0400 Subject: [PATCH 228/400] reduce diff --- vms/platformvm/state/state.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 841fe972278b..004d273a5edd 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2699,7 +2699,6 @@ func writePendingDiff( return nil } -// TODO: Add caching func (s *state) writeSubnetOnlyValidators() error { // Write modified weights: for subnetID, weight := range s.sovDiff.modifiedTotalWeight { @@ -2756,9 +2755,6 @@ func (s *state) writeSubnetOnlyValidators() error { } else { err = deleteSubnetOnlyValidator(s.inactiveDB, validationID) } - if err != nil { - return err - } case database.ErrNotFound: // This is a new validator subnetIDNodeID := subnetIDNodeID{ @@ -2766,10 +2762,9 @@ func (s *state) writeSubnetOnlyValidators() error { nodeID: sov.NodeID, } subnetIDNodeIDKey := subnetIDNodeID.Marshal() - if err := s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, validationID[:]); err != nil { - return err - } - default: + err = s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, validationID[:]) + } + if err != nil { return err } From 3e9dc01fd1327f151149ae749886d7b8a299db8a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 26 Oct 2024 20:49:51 -0400 Subject: [PATCH 229/400] reduce diff --- vms/platformvm/state/state.go | 6 ++++-- vms/platformvm/state/subnet_only_validator.go | 20 +++---------------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 004d273a5edd..07e99d505c9d 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2228,7 +2228,8 @@ func (s *state) getInheritedPublicKey(nodeID ids.NodeID) (*bls.PublicKey, error) // updateValidatorManager updates the validator manager with the pending // validator set changes. // -// This function must be called prior to writeCurrentStakers. +// This function must be called prior to writeCurrentStakers and +// writeSubnetOnlyValidators. func (s *state) updateValidatorManager(updateValidators bool) error { if !updateValidators { return nil @@ -2409,7 +2410,8 @@ func (s *state) updateValidatorManager(updateValidators bool) error { // writeValidatorDiffs writes the validator set diff contained by the pending // validator set changes to disk. // -// This function must be called prior to writeCurrentStakers. +// This function must be called prior to writeCurrentStakers and +// writeSubnetOnlyValidators. func (s *state) writeValidatorDiffs(height uint64) error { type validatorChanges struct { weightDiff ValidatorWeightDiff diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 6d2a0f6afb9e..cb40856efc59 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -243,26 +243,17 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state Chain, sov Subne return err } - switch { - case prevWeight < sov.Weight: + if prevWeight != sov.Weight { weight, err := state.WeightOfSubnetOnlyValidators(sov.SubnetID) if err != nil { return err } - weight, err = safemath.Add(weight, sov.Weight-prevWeight) + weight, err = safemath.Sub(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 - } - - weight, err = safemath.Sub(weight, prevWeight-sov.Weight) + weight, err = safemath.Add(weight, sov.Weight) if err != nil { return err } @@ -278,11 +269,6 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state Chain, sov Subne } 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 From da3a7267c73d69d052db7e2e08005f06817d9ad5 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 26 Oct 2024 20:52:54 -0400 Subject: [PATCH 230/400] reduce diff --- vms/platformvm/state/state.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 07e99d505c9d..e0e39a0f14a9 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -773,11 +773,7 @@ func (s *state) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { } // TODO: Add caching - weight, err := database.GetUInt64(s.weightsDB, subnetID[:]) - if err == database.ErrNotFound { - return 0, nil - } - return weight, err + return database.WithDefault(database.GetUInt64, s.weightsDB, subnetID[:], 0) } func (s *state) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { From 8483cedaaa4397d1c737d95326ee9cb143990e77 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 26 Oct 2024 21:05:18 -0400 Subject: [PATCH 231/400] cleanup --- vms/platformvm/state/diff.go | 6 +++--- vms/platformvm/state/state.go | 12 +++++------ vms/platformvm/state/subnet_only_validator.go | 21 ++++++++++++------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 47d98863106c..4b8922f74492 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -231,7 +231,7 @@ func (d *diff) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { func (d *diff) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { if sov, modified := d.sovDiff.modified[validationID]; modified { - if sov.Weight == 0 { + if sov.isDeleted() { return SubnetOnlyValidator{}, database.ErrNotFound } return sov, nil @@ -577,7 +577,7 @@ func (d *diff) Apply(baseState Chain) error { // 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 { + if !sov.isDeleted() { continue } if err := baseState.PutSubnetOnlyValidator(sov); err != nil { @@ -585,7 +585,7 @@ func (d *diff) Apply(baseState Chain) error { } } for _, sov := range d.sovDiff.modified { - if sov.Weight == 0 { + if sov.isDeleted() { continue } if err := baseState.PutSubnetOnlyValidator(sov); err != nil { diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index e0e39a0f14a9..421c2355e5a3 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -778,7 +778,7 @@ func (s *state) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { func (s *state) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { if sov, modified := s.sovDiff.modified[validationID]; modified { - if sov.Weight == 0 { + if sov.isDeleted() { return SubnetOnlyValidator{}, database.ErrNotFound } return sov, nil @@ -2283,7 +2283,7 @@ func (s *state) updateValidatorManager(updateValidators bool) error { sovChangesApplied set.Set[ids.ID] ) for validationID, sov := range sovChanges { - if sov.Weight != 0 { + if !sov.isDeleted() { // Additions and modifications are handled in the next loops. continue } @@ -2327,14 +2327,14 @@ func (s *state) updateValidatorManager(updateValidators bool) error { sovChangesApplied.Add(validationID) switch { - case !priorSOV.isActive() && sov.isActive(): + case priorSOV.isInactive() && 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(): + case priorSOV.isActive() && sov.isInactive(): // This validator is being deactivated. inactiveWeight := s.validators.GetWeight(sov.SubnetID, ids.EmptyNodeID) if inactiveWeight == 0 { @@ -2484,7 +2484,7 @@ func (s *state) writeValidatorDiffs(height uint64) error { // Perform SoV 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 { + if sov.isDeleted() { continue } @@ -2709,7 +2709,7 @@ func (s *state) writeSubnetOnlyValidators() error { sovChanges := s.sovDiff.modified // Perform deletions: for validationID, sov := range sovChanges { - if sov.Weight != 0 { + if !sov.isDeleted() { // Additions and modifications are handled in the next loops. continue } diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index cb40856efc59..932aa1a20cb2 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -139,10 +139,18 @@ func (v SubnetOnlyValidator) constantsAreUnmodified(o SubnetOnlyValidator) bool v.StartTime == o.StartTime } +func (v SubnetOnlyValidator) isDeleted() bool { + return v.Weight == 0 +} + func (v SubnetOnlyValidator) isActive() bool { return v.Weight != 0 && v.EndAccumulatedFee != 0 } +func (v SubnetOnlyValidator) isInactive() 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 { @@ -211,7 +219,7 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state Chain, sov Subne var ( prevWeight uint64 prevActive bool - newActive = sov.Weight != 0 && sov.EndAccumulatedFee != 0 + newActive = sov.isActive() ) switch priorSOV, err := state.GetSubnetOnlyValidator(sov.ValidationID); err { case nil: @@ -220,7 +228,7 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state Chain, sov Subne } prevWeight = priorSOV.Weight - prevActive = priorSOV.EndAccumulatedFee != 0 + prevActive = priorSOV.isActive() case database.ErrNotFound: // Verify that there is not a legacy subnet validator with the same // subnetID+nodeID as this L1 validator. @@ -277,12 +285,9 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state Chain, sov Subne 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.modifiedHasNodeIDs[subnetIDNodeID] = !sov.isDeleted() + if sov.isActive() { + d.active.ReplaceOrInsert(sov) } - d.active.ReplaceOrInsert(sov) return nil } From 0507ce702057b92bbf0b8b14680fd79c02b43fd4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 27 Oct 2024 14:36:10 -0400 Subject: [PATCH 232/400] 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 33ae0e61e2ee..ce4869792c61 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2054,7 +2054,7 @@ func (s *state) getInheritedPublicKey(nodeID ids.NodeID) (*bls.PublicKey, error) return vdr.validator.PublicKey, nil } if vdr, ok := s.currentStakers.validatorDiffs[constants.PrimaryNetworkID][nodeID]; ok && vdr.validator != nil { - // The primary network validator is being removed. + // The primary network validator is being modified. return vdr.validator.PublicKey, nil } return nil, fmt.Errorf("%w: %s", errMissingPrimaryNetworkValidator, nodeID) From 8bbeee650f5d3e9834ae9f4b8bb809b52b885ae9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 27 Oct 2024 15:49:29 -0400 Subject: [PATCH 233/400] Add comment --- vms/platformvm/state/subnet_only_validator.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 932aa1a20cb2..41014793c2ab 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -195,6 +195,8 @@ func newSubnetOnlyValidatorsDiff() *subnetOnlyValidatorsDiff { } } +// getActiveSubnetOnlyValidatorsIterator takes in the parent iterator, removes +// all modified validators, and then adds all modified active validators. func (d *subnetOnlyValidatorsDiff) getActiveSubnetOnlyValidatorsIterator(parentIterator iterator.Iterator[SubnetOnlyValidator]) iterator.Iterator[SubnetOnlyValidator] { return iterator.Merge( SubnetOnlyValidator.Less, From 08dd776bce7b5a727636f71f5f7f163662a4f023 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 27 Oct 2024 16:00:30 -0400 Subject: [PATCH 234/400] comment --- vms/platformvm/state/state.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index abedb5343cdb..f4ed7f26d7e5 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -787,6 +787,9 @@ func (s *state) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator return s.getPersistedSubnetOnlyValidator(validationID) } +// getPersistedSubnetOnlyValidator returns the currently persisted +// SubnetOnlyValidator with the given validationID. It is guaranteed that any +// returned validator is either active or inactive. func (s *state) getPersistedSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { if sov, ok := s.activeSOVLookup[validationID]; ok { return sov, nil @@ -2733,6 +2736,9 @@ func (s *state) writeSubnetOnlyValidators() error { s.activeSOVs.Delete(priorSOV) err = deleteSubnetOnlyValidator(s.activeDB, validationID) } else { + // It is technically possible for the validator not to exist on disk + // here, but that's fine as deleting an entry that doesn't exist is + // a noop. err = deleteSubnetOnlyValidator(s.inactiveDB, validationID) } if err != nil { From 255b0bf92c322f41536801d4ac8f41034719ef12 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 27 Oct 2024 16:15:45 -0400 Subject: [PATCH 235/400] nit --- vms/platformvm/state/subnet_only_validator.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 41014793c2ab..ddaa5eb5a50a 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -14,9 +14,8 @@ 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/math" "github.com/ava-labs/avalanchego/vms/platformvm/block" - - safemath "github.com/ava-labs/avalanchego/utils/math" ) var ( @@ -259,11 +258,11 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state Chain, sov Subne return err } - weight, err = safemath.Sub(weight, prevWeight) + weight, err = math.Sub(weight, prevWeight) if err != nil { return err } - weight, err = safemath.Add(weight, sov.Weight) + weight, err = math.Add(weight, sov.Weight) if err != nil { return err } From 3bc547d7741946ab731e6d29055c2e31deebe0ab Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 27 Oct 2024 18:19:52 -0400 Subject: [PATCH 236/400] reduce diff --- vms/platformvm/validators/manager.go | 94 +++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 7 deletions(-) diff --git a/vms/platformvm/validators/manager.go b/vms/platformvm/validators/manager.go index 2e67faa63464..142db3e7635c 100644 --- a/vms/platformvm/validators/manager.go +++ b/vms/platformvm/validators/manager.go @@ -200,7 +200,17 @@ func (m *manager) GetValidatorSet( // get the start time to track metrics startTime := m.clk.Time() - validatorSet, currentHeight, err := m.makeValidatorSet(ctx, targetHeight, subnetID) + + 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) + } if err != nil { return nil, err } @@ -233,12 +243,65 @@ func (m *manager) getValidatorSetCache(subnetID ids.ID) cache.Cacher[uint64, map return validatorSetsCache } -func (m *manager) makeValidatorSet( +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( ctx context.Context, targetHeight uint64, subnetID ids.ID, ) (map[ids.NodeID]*validators.GetValidatorOutput, uint64, error) { - subnetValidatorSet, currentHeight, err := m.getCurrentValidatorSet(ctx, subnetID) + subnetValidatorSet, primaryValidatorSet, currentHeight, err := m.getCurrentValidatorSets(ctx, subnetID) if err != nil { return nil, 0, err } @@ -269,23 +332,40 @@ func (m *manager) makeValidatorSet( 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 + } + } + err = m.state.ApplyValidatorPublicKeyDiffs( ctx, subnetValidatorSet, currentHeight, lastDiffHeight, - subnetID, + // TODO: Etna introduces L1s whose validators specify their own public + // keys, rather than inheriting them from the primary network. + // Therefore, this will need to use the subnetID after Etna. + constants.PrimaryNetworkID, ) return subnetValidatorSet, currentHeight, err } -func (m *manager) getCurrentValidatorSet( +func (m *manager) getCurrentValidatorSets( ctx context.Context, subnetID ids.ID, -) (map[ids.NodeID]*validators.GetValidatorOutput, uint64, error) { +) (map[ids.NodeID]*validators.GetValidatorOutput, 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, currentHeight, err + return subnetMap, primaryMap, currentHeight, err } func (m *manager) GetSubnetID(_ context.Context, chainID ids.ID) (ids.ID, error) { From 41f78f0915f4ffd54e7ca4572b6fe1de21faa6e5 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 29 Oct 2024 13:35:31 -0400 Subject: [PATCH 237/400] nit --- vms/platformvm/state/state.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 9372357270e8..1bbb4c1b1e9b 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -809,10 +809,11 @@ func (s *state) HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool return has, nil } - // TODO: Add caching - key := make([]byte, len(subnetID)+len(nodeID)) - copy(key, subnetID[:]) - copy(key[len(subnetID):], nodeID[:]) + subnetIDNodeID := subnetIDNodeID{ + subnetID: subnetID, + nodeID: nodeID, + } + key := subnetIDNodeID.Marshal() return s.subnetIDNodeIDDB.Has(key) } From 29cd6badb5c59965b4d18ed8b058a006d29abf66 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 29 Oct 2024 13:38:15 -0400 Subject: [PATCH 238/400] nit --- vms/platformvm/state/state.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 1bbb4c1b1e9b..68021ebebf0a 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -814,6 +814,8 @@ func (s *state) HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool nodeID: nodeID, } key := subnetIDNodeID.Marshal() + + // TODO: Add caching return s.subnetIDNodeIDDB.Has(key) } From 8dfcbb15570d7618722c338e541ea139455ce4b9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 29 Oct 2024 14:10:25 -0400 Subject: [PATCH 239/400] Fix initValidatorSets --- snow/validators/manager.go | 10 +++++ vms/platformvm/state/state.go | 10 ++--- vms/platformvm/state/state_test.go | 67 ++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/snow/validators/manager.go b/snow/validators/manager.go index 45ba32c0e261..6fd8a1b5f7c5 100644 --- a/snow/validators/manager.go +++ b/snow/validators/manager.go @@ -80,6 +80,9 @@ type Manager interface { // If an error is returned, the set will be unmodified. RemoveWeight(subnetID ids.ID, nodeID ids.NodeID, weight uint64) error + // NumSubnets returns the number of subnets with non-zero weight. + NumSubnets() int + // Count returns the number of validators currently in the subnet. Count(subnetID ids.ID) int @@ -227,6 +230,13 @@ func (m *manager) RemoveWeight(subnetID ids.ID, nodeID ids.NodeID, weight uint64 return nil } +func (m *manager) NumSubnets() int { + m.lock.RLock() + defer m.lock.RUnlock() + + return len(m.subnetToVdrs) +} + func (m *manager) Count(subnetID ids.ID) int { m.lock.RLock() set, exists := m.subnetToVdrs[subnetID] diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 68021ebebf0a..56cb2d85920e 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1867,6 +1867,11 @@ func (s *state) loadPendingValidators() error { // Invariant: initValidatorSets requires loadActiveSubnetOnlyValidators and // loadCurrentValidators to have already been called. func (s *state) initValidatorSets() error { + if s.validators.NumSubnets() != 0 { + // Enforce the invariant that the validator set is empty here. + return errValidatorSetAlreadyPopulated + } + // Load ACP77 validators for validationID, sov := range s.activeSOVLookup { pk := bls.PublicKeyFromValidUncompressedBytes(sov.PublicKey) @@ -1913,11 +1918,6 @@ func (s *state) initValidatorSets() error { // Load primary network and non-ACP77 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, subnetValidator := range subnetValidators { // The subnet validator's Public Key is inherited from the // corresponding primary network validator. diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 0ff795aefbdd..5dbf976e3270 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1944,6 +1944,73 @@ func TestSubnetOnlyValidators(t *testing.T) { } } +// TestLoadSubnetOnlyValidatorAndLegacy tests that the state can be loaded when +// there is a mix of legacy validators and subnet only validators in the same +// subnet. +func TestLoadSubnetOnlyValidatorAndLegacy(t *testing.T) { + var ( + require = require.New(t) + db = memdb.New() + state = newTestState(t, db) + subnetID = ids.GenerateTestID() + weight uint64 = 1 + ) + + unsignedAddSubnetValidator := createPermissionlessValidatorTx( + t, + subnetID, + txs.Validator{ + NodeID: defaultValidatorNodeID, + End: genesistest.DefaultValidatorEndTimeUnix, + Wght: weight, + }, + ) + addSubnetValidator := &txs.Tx{Unsigned: unsignedAddSubnetValidator} + require.NoError(addSubnetValidator.Initialize(txs.Codec)) + state.AddTx(addSubnetValidator, status.Committed) + + legacyStaker := &Staker{ + TxID: addSubnetValidator.ID(), + NodeID: defaultValidatorNodeID, + PublicKey: nil, + SubnetID: subnetID, + Weight: weight, + StartTime: genesistest.DefaultValidatorStartTime, + EndTime: genesistest.DefaultValidatorEndTime, + PotentialReward: 0, + } + require.NoError(state.PutCurrentValidator(legacyStaker)) + + sk, err := bls.NewSecretKey() + require.NoError(err) + pk := bls.PublicFromSecretKey(sk) + pkBytes := bls.PublicKeyToUncompressedBytes(pk) + + sov := SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: legacyStaker.SubnetID, + NodeID: ids.GenerateTestNodeID(), + PublicKey: pkBytes, + RemainingBalanceOwner: utils.RandomBytes(32), + DeactivationOwner: utils.RandomBytes(32), + StartTime: 1, + Weight: 2, + MinNonce: 3, + EndAccumulatedFee: 4, + } + require.NoError(state.PutSubnetOnlyValidator(sov)) + + state.SetHeight(1) + require.NoError(state.Commit()) + + expectedValidatorSet := state.validators.GetMap(subnetID) + + state = newTestState(t, db) + + validatorSet := state.validators.GetMap(subnetID) + require.Equal(expectedValidatorSet, validatorSet) +} + func TestSubnetOnlyValidatorAfterLegacyRemoval(t *testing.T) { require := require.New(t) From ef295480177969f4c4079073e54651bb1ef413be Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 29 Oct 2024 14:35:25 -0400 Subject: [PATCH 240/400] nit --- node/overridden_manager.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/node/overridden_manager.go b/node/overridden_manager.go index 484fe05da758..e961d2bd2d46 100644 --- a/node/overridden_manager.go +++ b/node/overridden_manager.go @@ -56,6 +56,10 @@ func (o *overriddenManager) RemoveWeight(_ ids.ID, nodeID ids.NodeID, weight uin return o.manager.RemoveWeight(o.subnetID, nodeID, weight) } +func (o *overriddenManager) NumSubnets() int { + return 1 +} + func (o *overriddenManager) Count(ids.ID) int { return o.manager.Count(o.subnetID) } From a77fb3c517811e9ee451c9aa3e4e5196fcb117af Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 29 Oct 2024 14:35:48 -0400 Subject: [PATCH 241/400] test subnetIDNodeIDDB --- vms/platformvm/state/state_test.go | 43 +++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 5dbf976e3270..50ef6b4919df 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1832,7 +1832,7 @@ func TestSubnetOnlyValidators(t *testing.T) { verifyChain := func(chain Chain) { for _, expectedSOV := range expectedSOVs { - if expectedSOV.Weight != 0 { + if !expectedSOV.isDeleted() { continue } @@ -1846,7 +1846,7 @@ func TestSubnetOnlyValidators(t *testing.T) { expectedActive []SubnetOnlyValidator ) for _, expectedSOV := range expectedSOVs { - if expectedSOV.Weight == 0 { + if expectedSOV.isDeleted() { continue } @@ -1893,13 +1893,50 @@ func TestSubnetOnlyValidators(t *testing.T) { verifyChain(state) assertChainsEqual(t, state, d) + // Verify that the subnetID+nodeID -> validationID mapping is correct. + var populatedSubnetIDNodeIDs set.Set[subnetIDNodeID] + for _, sov := range expectedSOVs { + if sov.isDeleted() { + continue + } + + subnetIDNodeID := subnetIDNodeID{ + subnetID: sov.SubnetID, + nodeID: sov.NodeID, + } + populatedSubnetIDNodeIDs.Add(subnetIDNodeID) + + subnetIDNodeIDKey := subnetIDNodeID.Marshal() + validatorID, err := database.GetID(state.subnetIDNodeIDDB, subnetIDNodeIDKey) + require.NoError(err) + require.Equal(sov.ValidationID, validatorID) + } + for _, sov := range expectedSOVs { + if !sov.isDeleted() { + continue + } + + subnetIDNodeID := subnetIDNodeID{ + subnetID: sov.SubnetID, + nodeID: sov.NodeID, + } + if populatedSubnetIDNodeIDs.Contains(subnetIDNodeID) { + continue + } + + subnetIDNodeIDKey := subnetIDNodeID.Marshal() + has, err := state.subnetIDNodeIDDB.Has(subnetIDNodeIDKey) + require.NoError(err) + require.False(has) + } + 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 { + if sov.SubnetID != subnetID || sov.isDeleted() { continue } From ce05dc86a64624e348c97c5fc328009cc6b27799 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 29 Oct 2024 14:40:54 -0400 Subject: [PATCH 242/400] fix comments --- vms/platformvm/state/state_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 50ef6b4919df..d65d2af9bf3c 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1730,7 +1730,7 @@ func TestSubnetOnlyValidators(t *testing.T) { NodeID: sov.NodeID, PublicKey: pkBytes, Weight: 2, // Not removed - EndAccumulatedFee: 1, // Inactive + EndAccumulatedFee: 1, // Active }, { ValidationID: sov.ValidationID, @@ -1738,7 +1738,7 @@ func TestSubnetOnlyValidators(t *testing.T) { NodeID: sov.NodeID, PublicKey: pkBytes, Weight: 3, // Not removed - EndAccumulatedFee: 1, // Inactive + EndAccumulatedFee: 1, // Active }, }, }, @@ -1768,7 +1768,7 @@ func TestSubnetOnlyValidators(t *testing.T) { NodeID: sov.NodeID, PublicKey: otherPKBytes, Weight: 1, // Not removed - EndAccumulatedFee: 1, // Inactive + EndAccumulatedFee: 1, // Active }, }, }, From 3d04cefefe4fb1999404b6ac016f0603931e1798 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 29 Oct 2024 14:45:53 -0400 Subject: [PATCH 243/400] nit --- node/overridden_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/overridden_manager.go b/node/overridden_manager.go index e961d2bd2d46..12c647b53fe4 100644 --- a/node/overridden_manager.go +++ b/node/overridden_manager.go @@ -56,7 +56,7 @@ func (o *overriddenManager) RemoveWeight(_ ids.ID, nodeID ids.NodeID, weight uin return o.manager.RemoveWeight(o.subnetID, nodeID, weight) } -func (o *overriddenManager) NumSubnets() int { +func (*overriddenManager) NumSubnets() int { return 1 } From dc35645a866cd8beec3f7e13844f18c2c74359d7 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 29 Oct 2024 14:46:22 -0400 Subject: [PATCH 244/400] nit --- node/overridden_manager.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/node/overridden_manager.go b/node/overridden_manager.go index 12c647b53fe4..61e37833f140 100644 --- a/node/overridden_manager.go +++ b/node/overridden_manager.go @@ -56,7 +56,10 @@ func (o *overriddenManager) RemoveWeight(_ ids.ID, nodeID ids.NodeID, weight uin return o.manager.RemoveWeight(o.subnetID, nodeID, weight) } -func (*overriddenManager) NumSubnets() int { +func (o *overriddenManager) NumSubnets() int { + if o.manager.Count(o.subnetID) == 0 { + return 0 + } return 1 } From d472a9f6ebe1dffa4ec34e21cb2b0ed46e40cf45 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 29 Oct 2024 16:07:39 -0400 Subject: [PATCH 245/400] Reduce diff --- vms/platformvm/state/state.go | 206 ++++++------------ vms/platformvm/state/state_test.go | 12 +- vms/platformvm/state/subnet_only_validator.go | 29 ++- 3 files changed, 91 insertions(+), 156 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 56cb2d85920e..e2451ecdeedb 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -34,7 +34,6 @@ import ( "github.com/ava-labs/avalanchego/utils/hashing" "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/timer" "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -2236,6 +2235,20 @@ func (s *state) getInheritedPublicKey(nodeID ids.NodeID) (*bls.PublicKey, error) return nil, fmt.Errorf("%w: %s", errMissingPrimaryNetworkValidator, nodeID) } +func (s *state) addSoVToValidatorManager(sov SubnetOnlyValidator) error { + nodeID := sov.effectiveNodeID() + if s.validators.GetWeight(sov.SubnetID, nodeID) != 0 { + return s.validators.AddWeight(sov.SubnetID, nodeID, sov.Weight) + } + return s.validators.AddStaker( + sov.SubnetID, + nodeID, + sov.effectivePublicKey(), + sov.effectiveValidationID(), + sov.Weight, + ) +} + // updateValidatorManager updates the validator manager with the pending // validator set changes. // @@ -2292,115 +2305,41 @@ func (s *state) updateValidatorManager(updateValidators bool) error { } } - // Perform SoV deletions: - var ( - sovChanges = s.sovDiff.modified - sovChangesApplied set.Set[ids.ID] - ) - for validationID, sov := range sovChanges { - if !sov.isDeleted() { - // Additions and modifications are handled in the next loops. - continue - } - - sovChangesApplied.Add(validationID) - - 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 - } - - 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 { - if sovChangesApplied.Contains(validationID) { - continue - } - + for validationID, sov := range s.sovDiff.modified { priorSOV, err := s.getPersistedSubnetOnlyValidator(validationID) - if err == database.ErrNotFound { - // New additions are handled in the next loop. - continue - } - if err != nil { - return err - } - - sovChangesApplied.Add(validationID) - - switch { - case priorSOV.isInactive() && 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.isInactive(): - // 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) + switch err { + case nil: + if sov.isDeleted() { + // Removing a validator + err = s.validators.RemoveWeight(priorSOV.SubnetID, priorSOV.effectiveNodeID(), priorSOV.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) + // Modifying a validator + if priorSOV.isActive() == sov.isActive() { + // This validator's active status isn't changing. This means + // the effectiveNodeIDs are equal. + nodeID := sov.effectiveNodeID() + 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) + } + } else { + // This validator's active status is changing. + err = errors.Join( + s.validators.RemoveWeight(sov.SubnetID, priorSOV.effectiveNodeID(), priorSOV.Weight), + s.addSoVToValidatorManager(sov), + ) + } } - } - if err != nil { - return err - } - } - - // Perform additions: - for validationID, sov := range sovChanges { - if sovChangesApplied.Contains(validationID) { - continue - } - - if sov.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) + case database.ErrNotFound: + if sov.isDeleted() { + // Deleting a non-existent validator is a noop. This can happen + // if the validator was added and then immediately removed. + continue } - continue - } - // This validator is inactive - var ( - inactiveWeight = s.validators.GetWeight(sov.SubnetID, ids.EmptyNodeID) - err error - ) - 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) + // Adding a validator + err = s.addSoVToValidatorManager(sov) } if err != nil { return err @@ -2467,61 +2406,40 @@ func (s *state) calculateValidatorDiffs() (map[subnetIDNodeID]*validatorDiff, er } } - // Perform SoV deletions: - for validationID := range s.sovDiff.modified { + // Calculate the changes to the ACP-77 validator set + for validationID, sov := range s.sovDiff.modified { priorSOV, err := s.getPersistedSubnetOnlyValidator(validationID) - if err == database.ErrNotFound { - continue - } - if err != nil { - return nil, err - } - - var ( - diff *validatorDiff - subnetIDNodeID = subnetIDNodeID{ + if err == nil { + // Delete the prior validator + subnetIDNodeID := subnetIDNodeID{ subnetID: priorSOV.SubnetID, + nodeID: priorSOV.effectiveNodeID(), } - ) - if priorSOV.isActive() { - subnetIDNodeID.nodeID = priorSOV.NodeID - diff = getOrDefault(changes, subnetIDNodeID) - diff.prevPublicKey = priorSOV.PublicKey - } else { - subnetIDNodeID.nodeID = ids.EmptyNodeID - diff = getOrDefault(changes, subnetIDNodeID) + diff := getOrDefault(changes, subnetIDNodeID) + if err := diff.weightDiff.Add(true, priorSOV.Weight); err != nil { + return nil, err + } + diff.prevPublicKey = priorSOV.effectivePublicKeyBytes() } - - if err := diff.weightDiff.Add(true, priorSOV.Weight); err != nil { + if err != database.ErrNotFound && err != nil { return nil, err } - } - // Perform SoV additions: - for _, sov := range s.sovDiff.modified { // If the validator is being removed, we shouldn't work to re-add it. if sov.isDeleted() { continue } - var ( - diff *validatorDiff - 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) + // Add the new validator + subnetIDNodeID := subnetIDNodeID{ + subnetID: sov.SubnetID, + nodeID: sov.effectiveNodeID(), } - + diff := getOrDefault(changes, subnetIDNodeID) if err := diff.weightDiff.Add(false, sov.Weight); err != nil { return nil, err } + diff.newPublicKey = sov.effectivePublicKeyBytes() } return changes, nil diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index d65d2af9bf3c..bc3ab907c7c7 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1940,20 +1940,12 @@ func TestSubnetOnlyValidators(t *testing.T) { 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 - } - + nodeID := sov.effectiveNodeID() vdr, ok := validatorSet[nodeID] if !ok { vdr = &validators.GetValidatorOutput{ NodeID: nodeID, - PublicKey: publicKey, + PublicKey: sov.effectivePublicKey(), } validatorSet[nodeID] = vdr } diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index ddaa5eb5a50a..24ea8bde739e 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -13,6 +13,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/crypto/bls" "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/vms/platformvm/block" @@ -146,8 +147,32 @@ func (v SubnetOnlyValidator) isActive() bool { return v.Weight != 0 && v.EndAccumulatedFee != 0 } -func (v SubnetOnlyValidator) isInactive() bool { - return v.Weight != 0 && v.EndAccumulatedFee == 0 +func (v SubnetOnlyValidator) effectiveValidationID() ids.ID { + if v.isActive() { + return v.ValidationID + } + return ids.Empty +} + +func (v SubnetOnlyValidator) effectiveNodeID() ids.NodeID { + if v.isActive() { + return v.NodeID + } + return ids.EmptyNodeID +} + +func (v SubnetOnlyValidator) effectivePublicKey() *bls.PublicKey { + if v.isActive() { + return bls.PublicKeyFromValidUncompressedBytes(v.PublicKey) + } + return nil +} + +func (v SubnetOnlyValidator) effectivePublicKeyBytes() []byte { + if v.isActive() { + return v.PublicKey + } + return nil } func getSubnetOnlyValidator(db database.KeyValueReader, validationID ids.ID) (SubnetOnlyValidator, error) { From 34ba29b13325af33ba3773617c907db80269a73f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 29 Oct 2024 16:16:46 -0400 Subject: [PATCH 246/400] add comment --- vms/platformvm/state/state.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index e2451ecdeedb..36c95516bab3 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1894,6 +1894,8 @@ func (s *state) initValidatorSets() error { return err } + // It is required for the SoVs to be loaded first so that the total + // weight is equal to the active weights here. activeWeight, err := s.validators.TotalWeight(subnetID) if err != nil { return err From 97029aa1e051df281456cecded55e1b67b730c7d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 30 Oct 2024 10:12:36 -0400 Subject: [PATCH 247/400] reduce diff --- 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 36c95516bab3..b9c975599f56 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2653,7 +2653,6 @@ func (s *state) writeSubnetOnlyValidators() error { return err } } - maps.Clear(s.sovDiff.modifiedTotalWeight) sovChanges := s.sovDiff.modified // Perform deletions: From dbeee70561cce248f44769117c8097fef29b7c48 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 30 Oct 2024 10:45:14 -0400 Subject: [PATCH 248/400] Add NumSubnets to the validator manager interface --- network/network.go | 2 +- node/node.go | 2 +- node/overridden_manager.go | 11 +++- snow/engine/snowman/bootstrap/bootstrapper.go | 2 +- .../snowman/bootstrap/bootstrapper_test.go | 4 +- snow/engine/snowman/syncer/state_syncer.go | 2 +- .../snowman/syncer/state_syncer_test.go | 12 ++-- snow/engine/snowman/syncer/utils_test.go | 2 +- snow/validators/manager.go | 16 ++++- snow/validators/manager_test.go | 61 +++++++++++++------ vms/platformvm/state/state.go | 10 +-- vms/platformvm/vm_test.go | 4 +- 12 files changed, 83 insertions(+), 45 deletions(-) diff --git a/network/network.go b/network/network.go index eab4ecca085e..816cbc69c7a2 100644 --- a/network/network.go +++ b/network/network.go @@ -778,7 +778,7 @@ func (n *network) samplePeers( // As an optimization, if there are fewer validators than // [numValidatorsToSample], only attempt to sample [numValidatorsToSample] // validators to potentially avoid iterating over the entire peer set. - numValidatorsToSample := min(config.Validators, n.config.Validators.Count(subnetID)) + numValidatorsToSample := min(config.Validators, n.config.Validators.NumValidators(subnetID)) n.peersLock.RLock() defer n.peersLock.RUnlock() diff --git a/node/node.go b/node/node.go index 1fedf35eb97e..8cdc8edfce17 100644 --- a/node/node.go +++ b/node/node.go @@ -600,7 +600,7 @@ func (n *Node) initNetworking(reg prometheus.Registerer) error { } n.onSufficientlyConnected = make(chan struct{}) - numBootstrappers := n.bootstrappers.Count(constants.PrimaryNetworkID) + numBootstrappers := n.bootstrappers.NumValidators(constants.PrimaryNetworkID) requiredConns := (3*numBootstrappers + 3) / 4 if requiredConns > 0 { diff --git a/node/overridden_manager.go b/node/overridden_manager.go index 484fe05da758..467dd7df1fa7 100644 --- a/node/overridden_manager.go +++ b/node/overridden_manager.go @@ -56,8 +56,15 @@ func (o *overriddenManager) RemoveWeight(_ ids.ID, nodeID ids.NodeID, weight uin return o.manager.RemoveWeight(o.subnetID, nodeID, weight) } -func (o *overriddenManager) Count(ids.ID) int { - return o.manager.Count(o.subnetID) +func (o *overriddenManager) NumSubnets() int { + if o.manager.NumValidators(o.subnetID) == 0 { + return 0 + } + return 1 +} + +func (o *overriddenManager) NumValidators(ids.ID) int { + return o.manager.NumValidators(o.subnetID) } func (o *overriddenManager) TotalWeight(ids.ID) (uint64, error) { diff --git a/snow/engine/snowman/bootstrap/bootstrapper.go b/snow/engine/snowman/bootstrap/bootstrapper.go index 024925c79288..4b0b511910f0 100644 --- a/snow/engine/snowman/bootstrap/bootstrapper.go +++ b/snow/engine/snowman/bootstrap/bootstrapper.go @@ -300,7 +300,7 @@ func (b *Bootstrapper) sendBootstrappingMessagesOrFinish(ctx context.Context) er if numAccepted == 0 { b.Ctx.Log.Debug("restarting bootstrap", zap.String("reason", "no blocks accepted"), - zap.Int("numBeacons", b.Beacons.Count(b.Ctx.SubnetID)), + zap.Int("numBeacons", b.Beacons.NumValidators(b.Ctx.SubnetID)), ) // Invariant: These functions are mutualy recursive. However, when // [startBootstrapping] calls [sendMessagesOrFinish], it is guaranteed diff --git a/snow/engine/snowman/bootstrap/bootstrapper_test.go b/snow/engine/snowman/bootstrap/bootstrapper_test.go index 772cf51281e9..7803f82a725f 100644 --- a/snow/engine/snowman/bootstrap/bootstrapper_test.go +++ b/snow/engine/snowman/bootstrap/bootstrapper_test.go @@ -98,7 +98,7 @@ func newConfig(t *testing.T) (Config, ids.NodeID, *enginetest.Sender, *blocktest AllGetsServer: snowGetHandler, Ctx: ctx, Beacons: vdrs, - SampleK: vdrs.Count(ctx.SubnetID), + SampleK: vdrs.NumValidators(ctx.SubnetID), StartupTracker: startupTracker, PeerTracker: peerTracker, Sender: sender, @@ -693,7 +693,7 @@ func TestBootstrapNoParseOnNew(t *testing.T) { AllGetsServer: snowGetHandler, Ctx: ctx, Beacons: peers, - SampleK: peers.Count(ctx.SubnetID), + SampleK: peers.NumValidators(ctx.SubnetID), StartupTracker: startupTracker, PeerTracker: peerTracker, Sender: sender, diff --git a/snow/engine/snowman/syncer/state_syncer.go b/snow/engine/snowman/syncer/state_syncer.go index 76e647e73a64..7e669fee2534 100644 --- a/snow/engine/snowman/syncer/state_syncer.go +++ b/snow/engine/snowman/syncer/state_syncer.go @@ -373,7 +373,7 @@ func (ss *stateSyncer) AcceptedStateSummary(ctx context.Context, nodeID ids.Node if votingStakes < ss.Alpha { ss.Ctx.Log.Debug("restarting state sync", zap.String("reason", "not enough votes received"), - zap.Int("numBeacons", ss.StateSyncBeacons.Count(ss.Ctx.SubnetID)), + zap.Int("numBeacons", ss.StateSyncBeacons.NumValidators(ss.Ctx.SubnetID)), zap.Int("numFailedSyncers", ss.failedVoters.Len()), ) return ss.startup(ctx) diff --git a/snow/engine/snowman/syncer/state_syncer_test.go b/snow/engine/snowman/syncer/state_syncer_test.go index fd062cb2d8b1..ffb7de7ffca7 100644 --- a/snow/engine/snowman/syncer/state_syncer_test.go +++ b/snow/engine/snowman/syncer/state_syncer_test.go @@ -247,7 +247,7 @@ func TestBeaconsAreReachedForFrontiersUponStartup(t *testing.T) { } // check that vdrs are reached out for frontiers - require.Len(contactedFrontiersProviders, min(beacons.Count(ctx.SubnetID), maxOutstandingBroadcastRequests)) + require.Len(contactedFrontiersProviders, min(beacons.NumValidators(ctx.SubnetID), maxOutstandingBroadcastRequests)) for beaconID := range contactedFrontiersProviders { // check that beacon is duly marked as reached out require.Contains(syncer.pendingSeeders, beaconID) @@ -344,7 +344,7 @@ func TestUnRequestedStateSummaryFrontiersAreDropped(t *testing.T) { // other listed vdrs are reached for data require.True( len(contactedFrontiersProviders) > initiallyReachedOutBeaconsSize || - len(contactedFrontiersProviders) == beacons.Count(ctx.SubnetID)) + len(contactedFrontiersProviders) == beacons.NumValidators(ctx.SubnetID)) } func TestMalformedStateSummaryFrontiersAreDropped(t *testing.T) { @@ -413,7 +413,7 @@ func TestMalformedStateSummaryFrontiersAreDropped(t *testing.T) { // are reached for data require.True( len(contactedFrontiersProviders) > initiallyReachedOutBeaconsSize || - len(contactedFrontiersProviders) == beacons.Count(ctx.SubnetID)) + len(contactedFrontiersProviders) == beacons.NumValidators(ctx.SubnetID)) } func TestLateResponsesFromUnresponsiveFrontiersAreNotRecorded(t *testing.T) { @@ -475,7 +475,7 @@ func TestLateResponsesFromUnresponsiveFrontiersAreNotRecorded(t *testing.T) { // are reached for data require.True( len(contactedFrontiersProviders) > initiallyReachedOutBeaconsSize || - len(contactedFrontiersProviders) == beacons.Count(ctx.SubnetID)) + len(contactedFrontiersProviders) == beacons.NumValidators(ctx.SubnetID)) // mock VM to simulate a valid but late summary is returned fullVM.CantParseStateSummary = true @@ -773,7 +773,7 @@ func TestUnRequestedVotesAreDropped(t *testing.T) { // other listed voters are reached out require.True( len(contactedVoters) > initiallyContactedVotersSize || - len(contactedVoters) == beacons.Count(ctx.SubnetID)) + len(contactedVoters) == beacons.NumValidators(ctx.SubnetID)) } func TestVotesForUnknownSummariesAreDropped(t *testing.T) { @@ -876,7 +876,7 @@ func TestVotesForUnknownSummariesAreDropped(t *testing.T) { // on unknown summary require.True( len(contactedVoters) > initiallyContactedVotersSize || - len(contactedVoters) == beacons.Count(ctx.SubnetID)) + len(contactedVoters) == beacons.NumValidators(ctx.SubnetID)) } func TestStateSummaryIsPassedToVMAsMajorityOfVotesIsCastedForIt(t *testing.T) { diff --git a/snow/engine/snowman/syncer/utils_test.go b/snow/engine/snowman/syncer/utils_test.go index 4cd6e58d840e..d13b7347771b 100644 --- a/snow/engine/snowman/syncer/utils_test.go +++ b/snow/engine/snowman/syncer/utils_test.go @@ -107,7 +107,7 @@ func buildTestsObjects( startupTracker, sender, beacons, - beacons.Count(ctx.SubnetID), + beacons.NumValidators(ctx.SubnetID), alpha, nil, fullVM, diff --git a/snow/validators/manager.go b/snow/validators/manager.go index 45ba32c0e261..dd2a8f88d1f8 100644 --- a/snow/validators/manager.go +++ b/snow/validators/manager.go @@ -80,8 +80,11 @@ type Manager interface { // If an error is returned, the set will be unmodified. RemoveWeight(subnetID ids.ID, nodeID ids.NodeID, weight uint64) error - // Count returns the number of validators currently in the subnet. - Count(subnetID ids.ID) int + // NumSubnets returns the number of subnets with non-zero weight. + NumSubnets() int + + // NumValidators returns the number of validators currently in the subnet. + NumValidators(subnetID ids.ID) int // TotalWeight returns the cumulative weight of all validators in the subnet. // Returns err if total weight overflows uint64. @@ -227,7 +230,14 @@ func (m *manager) RemoveWeight(subnetID ids.ID, nodeID ids.NodeID, weight uint64 return nil } -func (m *manager) Count(subnetID ids.ID) int { +func (m *manager) NumSubnets() int { + m.lock.RLock() + defer m.lock.RUnlock() + + return len(m.subnetToVdrs) +} + +func (m *manager) NumValidators(subnetID ids.ID) int { m.lock.RLock() set, exists := m.subnetToVdrs[subnetID] m.lock.RUnlock() diff --git a/snow/validators/manager_test.go b/snow/validators/manager_test.go index 365d7ffdf7d7..4449a324a57d 100644 --- a/snow/validators/manager_test.go +++ b/snow/validators/manager_test.go @@ -242,36 +242,57 @@ func TestGet(t *testing.T) { require.False(ok) } -func TestLen(t *testing.T) { - require := require.New(t) +func TestNum(t *testing.T) { + var ( + require = require.New(t) - m := NewManager() - subnetID := ids.GenerateTestID() + m = NewManager() - count := m.Count(subnetID) - require.Zero(count) + subnetID0 = ids.GenerateTestID() + subnetID1 = ids.GenerateTestID() + nodeID0 = ids.GenerateTestNodeID() + nodeID1 = ids.GenerateTestNodeID() + ) - nodeID0 := ids.GenerateTestNodeID() - require.NoError(m.AddStaker(subnetID, nodeID0, nil, ids.Empty, 1)) + require.Zero(m.NumSubnets()) + require.Zero(m.NumValidators(subnetID0)) + require.Zero(m.NumValidators(subnetID1)) - count = m.Count(subnetID) - require.Equal(1, count) + require.NoError(m.AddStaker(subnetID0, nodeID0, nil, ids.Empty, 1)) - nodeID1 := ids.GenerateTestNodeID() - require.NoError(m.AddStaker(subnetID, nodeID1, nil, ids.Empty, 1)) + require.Equal(1, m.NumSubnets()) + require.Equal(1, m.NumValidators(subnetID0)) + require.Zero(m.NumValidators(subnetID1)) - count = m.Count(subnetID) - require.Equal(2, count) + require.NoError(m.AddStaker(subnetID0, nodeID1, nil, ids.Empty, 1)) - require.NoError(m.RemoveWeight(subnetID, nodeID1, 1)) + require.Equal(1, m.NumSubnets()) + require.Equal(2, m.NumValidators(subnetID0)) + require.Zero(m.NumValidators(subnetID1)) - count = m.Count(subnetID) - require.Equal(1, count) + require.NoError(m.AddStaker(subnetID1, nodeID1, nil, ids.Empty, 2)) - require.NoError(m.RemoveWeight(subnetID, nodeID0, 1)) + require.Equal(2, m.NumSubnets()) + require.Equal(2, m.NumValidators(subnetID0)) + require.Equal(1, m.NumValidators(subnetID1)) + + require.NoError(m.RemoveWeight(subnetID0, nodeID1, 1)) + + require.Equal(2, m.NumSubnets()) + require.Equal(1, m.NumValidators(subnetID0)) + require.Equal(1, m.NumValidators(subnetID1)) + + require.NoError(m.RemoveWeight(subnetID0, nodeID0, 1)) + + require.Equal(1, m.NumSubnets()) + require.Zero(m.NumValidators(subnetID0)) + require.Equal(1, m.NumValidators(subnetID1)) + + require.NoError(m.RemoveWeight(subnetID1, nodeID1, 2)) - count = m.Count(subnetID) - require.Zero(count) + require.Zero(m.NumSubnets()) + require.Zero(m.NumValidators(subnetID0)) + require.Zero(m.NumValidators(subnetID1)) } func TestGetMap(t *testing.T) { diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index be6bee28cf0f..09a22f8c27de 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1750,13 +1750,13 @@ func (s *state) loadPendingValidators() error { // Invariant: initValidatorSets requires loadCurrentValidators to have already // been called. func (s *state) initValidatorSets() error { + if s.validators.NumSubnets() != 0 { + // Enforce the invariant that the validator set is empty here. + return errValidatorSetAlreadyPopulated + } + 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, subnetValidator := range subnetValidators { // The subnet validator's Public Key is inherited from the // corresponding primary network validator. diff --git a/vms/platformvm/vm_test.go b/vms/platformvm/vm_test.go index 7048778b19bd..f377fd31f894 100644 --- a/vms/platformvm/vm_test.go +++ b/vms/platformvm/vm_test.go @@ -283,7 +283,7 @@ func TestGenesis(t *testing.T) { } // Ensure current validator set of primary network is correct - require.Len(genesisState.Validators, vm.Validators.Count(constants.PrimaryNetworkID)) + require.Len(genesisState.Validators, vm.Validators.NumValidators(constants.PrimaryNetworkID)) for _, nodeID := range genesistest.DefaultNodeIDs { _, ok := vm.Validators.GetValidator(constants.PrimaryNetworkID, nodeID) @@ -1326,7 +1326,7 @@ func TestBootstrapPartiallyAccepted(t *testing.T) { AllGetsServer: snowGetHandler, Ctx: consensusCtx, Beacons: beacons, - SampleK: beacons.Count(ctx.SubnetID), + SampleK: beacons.NumValidators(ctx.SubnetID), StartupTracker: startup, PeerTracker: peerTracker, Sender: sender, From 3621e53fa19e65df8ab977915876a29b648f9737 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 30 Oct 2024 12:50:25 -0400 Subject: [PATCH 249/400] add comment --- vms/platformvm/state/state_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index bc3ab907c7c7..df6e92c7b088 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -2040,6 +2040,8 @@ func TestLoadSubnetOnlyValidatorAndLegacy(t *testing.T) { require.Equal(expectedValidatorSet, validatorSet) } +// TestSubnetOnlyValidatorAfterLegacyRemoval verifies that a legacy validator +// can be replaced by an SoV in the same block. func TestSubnetOnlyValidatorAfterLegacyRemoval(t *testing.T) { require := require.New(t) From 92a2277da4f7daf1880d5eb5a9c623a0012e000c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 30 Oct 2024 13:19:44 -0400 Subject: [PATCH 250/400] Delete empty entries --- vms/platformvm/state/state.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index b9c975599f56..488e593779b8 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2649,7 +2649,13 @@ func writePendingDiff( func (s *state) writeSubnetOnlyValidators() error { // Write modified weights: for subnetID, weight := range s.sovDiff.modifiedTotalWeight { - if err := database.PutUInt64(s.weightsDB, subnetID[:], weight); err != nil { + var err error + if weight == 0 { + err = s.weightsDB.Delete(subnetID[:]) + } else { + err = database.PutUInt64(s.weightsDB, subnetID[:], weight) + } + if err != nil { return err } } From 6375aa2667a9fb4013b3ade5ab9a2788dd04d215 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 30 Oct 2024 17:36:43 -0400 Subject: [PATCH 251/400] simplify state futher --- vms/platformvm/state/state.go | 90 ++++++------------- vms/platformvm/state/subnet_only_validator.go | 52 +++++++++++ 2 files changed, 78 insertions(+), 64 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 488e593779b8..2dc3dfa0143a 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -329,8 +329,7 @@ type state struct { expiryDiff *expiryDiff expiryDB database.Database - activeSOVLookup map[ids.ID]SubnetOnlyValidator - activeSOVs *btree.BTreeG[SubnetOnlyValidator] + activeSOVs *activeSubnetOnlyValidators sovDiff *subnetOnlyValidatorsDiff subnetOnlyValidatorsDB database.Database weightsDB database.Database @@ -657,8 +656,7 @@ func New( expiryDiff: newExpiryDiff(), expiryDB: prefixdb.New(ExpiryReplayProtectionPrefix, baseDB), - activeSOVLookup: make(map[ids.ID]SubnetOnlyValidator), - activeSOVs: btree.NewG(defaultTreeDegree, SubnetOnlyValidator.Less), + activeSOVs: newActiveSubnetOnlyValidators(), sovDiff: newSubnetOnlyValidatorsDiff(), subnetOnlyValidatorsDB: subnetOnlyValidatorsDB, weightsDB: prefixdb.New(WeightsPrefix, subnetOnlyValidatorsDB), @@ -763,12 +761,12 @@ func (s *state) DeleteExpiry(entry ExpiryEntry) { func (s *state) GetActiveSubnetOnlyValidatorsIterator() (iterator.Iterator[SubnetOnlyValidator], error) { return s.sovDiff.getActiveSubnetOnlyValidatorsIterator( - iterator.FromTree(s.activeSOVs), + s.activeSOVs.newIterator(), ), nil } func (s *state) NumActiveSubnetOnlyValidators() int { - return len(s.activeSOVLookup) + s.sovDiff.numAddedActive + return s.activeSOVs.len() + s.sovDiff.numAddedActive } func (s *state) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { @@ -795,7 +793,7 @@ func (s *state) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator // SubnetOnlyValidator with the given validationID. It is guaranteed that any // returned validator is either active or inactive. func (s *state) getPersistedSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { - if sov, ok := s.activeSOVLookup[validationID]; ok { + if sov, ok := s.activeSOVs.get(validationID); ok { return sov, nil } @@ -1602,8 +1600,7 @@ func (s *state) loadActiveSubnetOnlyValidators() error { return fmt.Errorf("failed to unmarshal SubnetOnlyValidator: %w", err) } - s.activeSOVLookup[validationID] = sov - s.activeSOVs.ReplaceOrInsert(sov) + s.activeSOVs.put(sov) } return nil @@ -1872,11 +1869,8 @@ 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 - } + if err := s.activeSOVs.addStakers(s.validators); err != nil { + return err } // Load inactive weights @@ -2660,72 +2654,40 @@ func (s *state) writeSubnetOnlyValidators() error { } } - sovChanges := s.sovDiff.modified - // Perform deletions: - for validationID, sov := range sovChanges { - if !sov.isDeleted() { - // Additions and modifications are handled in the next loops. - continue + for validationID, sov := range s.sovDiff.modified { + // Delete the prior validator if it exists + var err error + if s.activeSOVs.delete(validationID) { + err = deleteSubnetOnlyValidator(s.activeDB, validationID) + } else { + err = deleteSubnetOnlyValidator(s.inactiveDB, validationID) + } + if err != nil { + return err } - // The next loops shouldn't consider this change. - delete(sovChanges, validationID) - + // Update the subnetIDNodeID mapping subnetIDNodeID := subnetIDNodeID{ subnetID: sov.SubnetID, nodeID: sov.NodeID, } subnetIDNodeIDKey := subnetIDNodeID.Marshal() - err := s.subnetIDNodeIDDB.Delete(subnetIDNodeIDKey) - if err != nil { - return err - } - - priorSOV, wasActive := s.activeSOVLookup[validationID] - if wasActive { - delete(s.activeSOVLookup, validationID) - s.activeSOVs.Delete(priorSOV) - err = deleteSubnetOnlyValidator(s.activeDB, validationID) + if sov.isDeleted() { + err = s.subnetIDNodeIDDB.Delete(subnetIDNodeIDKey) } else { - // It is technically possible for the validator not to exist on disk - // here, but that's fine as deleting an entry that doesn't exist is - // a noop. - err = deleteSubnetOnlyValidator(s.inactiveDB, validationID) + err = s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, validationID[:]) } if err != nil { return err } - } - // Perform modifications and additions: - for validationID, sov := range sovChanges { - priorSOV, err := s.getPersistedSubnetOnlyValidator(validationID) - switch err { - case nil: - // This is modifying an existing validator - if priorSOV.isActive() { - delete(s.activeSOVLookup, validationID) - s.activeSOVs.Delete(priorSOV) - err = deleteSubnetOnlyValidator(s.activeDB, validationID) - } else { - err = deleteSubnetOnlyValidator(s.inactiveDB, validationID) - } - case database.ErrNotFound: - // This is a new validator - subnetIDNodeID := subnetIDNodeID{ - subnetID: sov.SubnetID, - nodeID: sov.NodeID, - } - subnetIDNodeIDKey := subnetIDNodeID.Marshal() - err = s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, validationID[:]) - } - if err != nil { - return err + if sov.isDeleted() { + continue } + // Add the new validator if sov.isActive() { - s.activeSOVLookup[validationID] = sov - s.activeSOVs.ReplaceOrInsert(sov) + s.activeSOVs.put(sov) err = putSubnetOnlyValidator(s.activeDB, sov) } else { err = putSubnetOnlyValidator(s.inactiveDB, sov) diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 24ea8bde739e..0814e304d068 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/iterator" @@ -317,3 +318,54 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state Chain, sov Subne } return nil } + +type activeSubnetOnlyValidators struct { + lookup map[ids.ID]SubnetOnlyValidator + tree *btree.BTreeG[SubnetOnlyValidator] +} + +func newActiveSubnetOnlyValidators() *activeSubnetOnlyValidators { + return &activeSubnetOnlyValidators{ + lookup: make(map[ids.ID]SubnetOnlyValidator), + tree: btree.NewG(defaultTreeDegree, SubnetOnlyValidator.Less), + } +} + +func (a *activeSubnetOnlyValidators) get(validationID ids.ID) (SubnetOnlyValidator, bool) { + sov, ok := a.lookup[validationID] + return sov, ok +} + +func (a *activeSubnetOnlyValidators) put(sov SubnetOnlyValidator) { + a.lookup[sov.ValidationID] = sov + a.tree.ReplaceOrInsert(sov) +} + +func (a *activeSubnetOnlyValidators) delete(validationID ids.ID) bool { + sov, ok := a.lookup[validationID] + if !ok { + return false + } + + delete(a.lookup, validationID) + a.tree.Delete(sov) + return true +} + +func (a *activeSubnetOnlyValidators) len() int { + return len(a.lookup) +} + +func (a *activeSubnetOnlyValidators) newIterator() iterator.Iterator[SubnetOnlyValidator] { + return iterator.FromTree(a.tree) +} + +func (a *activeSubnetOnlyValidators) addStakers(vdrs validators.Manager) error { + for validationID, sov := range a.lookup { + pk := bls.PublicKeyFromValidUncompressedBytes(sov.PublicKey) + if err := vdrs.AddStaker(sov.SubnetID, sov.NodeID, pk, validationID, sov.Weight); err != nil { + return err + } + } + return nil +} From aedff159444e006e901d4c7352049281edfbf58a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 30 Oct 2024 17:44:25 -0400 Subject: [PATCH 252/400] 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 2dc3dfa0143a..54e07902e3a8 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2641,7 +2641,7 @@ func writePendingDiff( } func (s *state) writeSubnetOnlyValidators() error { - // Write modified weights: + // Write modified weights for subnetID, weight := range s.sovDiff.modifiedTotalWeight { var err error if weight == 0 { From 1ac030ac06f5b24f79bef12ae69e05a0b7adbe16 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 30 Oct 2024 17:46:49 -0400 Subject: [PATCH 253/400] nit --- vms/platformvm/state/state.go | 18 ++---------------- vms/platformvm/state/subnet_only_validator.go | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 54e07902e3a8..cbdda6537a53 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2231,20 +2231,6 @@ func (s *state) getInheritedPublicKey(nodeID ids.NodeID) (*bls.PublicKey, error) return nil, fmt.Errorf("%w: %s", errMissingPrimaryNetworkValidator, nodeID) } -func (s *state) addSoVToValidatorManager(sov SubnetOnlyValidator) error { - nodeID := sov.effectiveNodeID() - if s.validators.GetWeight(sov.SubnetID, nodeID) != 0 { - return s.validators.AddWeight(sov.SubnetID, nodeID, sov.Weight) - } - return s.validators.AddStaker( - sov.SubnetID, - nodeID, - sov.effectivePublicKey(), - sov.effectiveValidationID(), - sov.Weight, - ) -} - // updateValidatorManager updates the validator manager with the pending // validator set changes. // @@ -2323,7 +2309,7 @@ func (s *state) updateValidatorManager(updateValidators bool) error { // This validator's active status is changing. err = errors.Join( s.validators.RemoveWeight(sov.SubnetID, priorSOV.effectiveNodeID(), priorSOV.Weight), - s.addSoVToValidatorManager(sov), + addSoVToValidatorManager(s.validators, sov), ) } } @@ -2335,7 +2321,7 @@ func (s *state) updateValidatorManager(updateValidators bool) error { } // Adding a validator - err = s.addSoVToValidatorManager(sov) + err = addSoVToValidatorManager(s.validators, sov) } if err != nil { return err diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 0814e304d068..a299cd478c3e 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -369,3 +369,17 @@ func (a *activeSubnetOnlyValidators) addStakers(vdrs validators.Manager) error { } return nil } + +func addSoVToValidatorManager(vdrs validators.Manager, sov SubnetOnlyValidator) error { + nodeID := sov.effectiveNodeID() + if vdrs.GetWeight(sov.SubnetID, nodeID) != 0 { + return vdrs.AddWeight(sov.SubnetID, nodeID, sov.Weight) + } + return vdrs.AddStaker( + sov.SubnetID, + nodeID, + sov.effectivePublicKey(), + sov.effectiveValidationID(), + sov.Weight, + ) +} From 9e0d7d5efe5ef7fc801b7a747d08123a515f37a4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Oct 2024 11:03:00 -0400 Subject: [PATCH 254/400] Add caching --- vms/platformvm/config/execution_config.go | 54 +++++---- .../config/execution_config_test.go | 25 ++-- vms/platformvm/state/state.go | 107 +++++++++++++++--- 3 files changed, 137 insertions(+), 49 deletions(-) diff --git a/vms/platformvm/config/execution_config.go b/vms/platformvm/config/execution_config.go index e5bef1637d05..2c63c6299e58 100644 --- a/vms/platformvm/config/execution_config.go +++ b/vms/platformvm/config/execution_config.go @@ -12,34 +12,40 @@ import ( ) var DefaultExecutionConfig = ExecutionConfig{ - Network: network.DefaultConfig, - BlockCacheSize: 64 * units.MiB, - TxCacheSize: 128 * units.MiB, - TransformedSubnetTxCacheSize: 4 * units.MiB, - RewardUTXOsCacheSize: 2048, - ChainCacheSize: 2048, - ChainDBCacheSize: 2048, - BlockIDCacheSize: 8192, - FxOwnerCacheSize: 4 * units.MiB, - SubnetConversionCacheSize: 4 * units.MiB, - ChecksumsEnabled: false, - MempoolPruneFrequency: 30 * time.Minute, + Network: network.DefaultConfig, + BlockCacheSize: 64 * units.MiB, + TxCacheSize: 128 * units.MiB, + TransformedSubnetTxCacheSize: 4 * units.MiB, + RewardUTXOsCacheSize: 2048, + ChainCacheSize: 2048, + ChainDBCacheSize: 2048, + BlockIDCacheSize: 8192, + FxOwnerCacheSize: 4 * units.MiB, + SubnetConversionCacheSize: 4 * units.MiB, + L1WeightsCacheSize: 16 * units.KiB, + L1InactiveValidatorsCacheSize: 256 * units.KiB, + L1SubnetIDNodeIDCacheSize: 16 * units.KiB, + ChecksumsEnabled: false, + MempoolPruneFrequency: 30 * time.Minute, } // 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"` - SubnetConversionCacheSize int `json:"subnet-conversion-cache-size"` - ChecksumsEnabled bool `json:"checksums-enabled"` - MempoolPruneFrequency time.Duration `json:"mempool-prune-frequency"` + 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"` + SubnetConversionCacheSize int `json:"subnet-conversion-cache-size"` + L1WeightsCacheSize int `json:"l1-weights-cache-size"` + L1InactiveValidatorsCacheSize int `json:"l1-inactive-validators-cache-size"` + L1SubnetIDNodeIDCacheSize int `json:"l1-subnet-id-node-id-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 c938c177add3..f4b077689b23 100644 --- a/vms/platformvm/config/execution_config_test.go +++ b/vms/platformvm/config/execution_config_test.go @@ -81,17 +81,20 @@ func TestExecutionConfigUnmarshal(t *testing.T) { ExpectedBloomFilterFalsePositiveProbability: 16, MaxBloomFilterFalsePositiveProbability: 17, }, - BlockCacheSize: 1, - TxCacheSize: 2, - TransformedSubnetTxCacheSize: 3, - RewardUTXOsCacheSize: 5, - ChainCacheSize: 6, - ChainDBCacheSize: 7, - BlockIDCacheSize: 8, - FxOwnerCacheSize: 9, - SubnetConversionCacheSize: 10, - ChecksumsEnabled: true, - MempoolPruneFrequency: time.Minute, + BlockCacheSize: 1, + TxCacheSize: 2, + TransformedSubnetTxCacheSize: 3, + RewardUTXOsCacheSize: 5, + ChainCacheSize: 6, + ChainDBCacheSize: 7, + BlockIDCacheSize: 8, + FxOwnerCacheSize: 9, + SubnetConversionCacheSize: 10, + L1WeightsCacheSize: 11, + L1InactiveValidatorsCacheSize: 12, + L1SubnetIDNodeIDCacheSize: 13, + ChecksumsEnabled: true, + MempoolPruneFrequency: time.Minute, } verifyInitializedStruct(t, *expected) verifyInitializedStruct(t, expected.Network) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index cbdda6537a53..b059eeb4595e 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -34,6 +34,7 @@ import ( "github.com/ava-labs/avalanchego/utils/hashing" "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/timer" "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -86,7 +87,7 @@ var ( SupplyPrefix = []byte("supply") ChainPrefix = []byte("chain") ExpiryReplayProtectionPrefix = []byte("expiryReplayProtection") - SubnetOnlyValidatorsPrefix = []byte("subnetOnlyValidators") + SubnetOnlyPrefix = []byte("subnetOnly") WeightsPrefix = []byte("weights") SubnetIDNodeIDPrefix = []byte("subnetIDNodeID") ActivePrefix = []byte("active") @@ -273,6 +274,15 @@ type stateBlk struct { * | | '-. subnetDelegator * | | '-. list * | | '-- txID -> nil + * | |-. subnetOnly + * | | |-. weights + * | | | '-- subnetID -> weight + * | | |-. subnetIDNodeID + * | | | '-- subnetID+nodeID -> validationID + * | | |-. active + * | | | '-- validationID -> subnetOnlyValidator + * | | '-. inactive + * | | '-- validationID -> subnetOnlyValidator * | |-. weight diffs * | | '-- subnet+height+nodeID -> weightChange * | '-. pub key diffs @@ -332,9 +342,12 @@ type state struct { activeSOVs *activeSubnetOnlyValidators sovDiff *subnetOnlyValidatorsDiff subnetOnlyValidatorsDB database.Database + weightsCache cache.Cacher[ids.ID, uint64] // subnetID -> total SoV weight weightsDB database.Database + subnetIDNodeIDCache cache.Cacher[subnetIDNodeID, bool] // subnetID+nodeID -> is validator subnetIDNodeIDDB database.Database activeDB database.Database + inactiveCache cache.Cacher[ids.ID, maybe.Maybe[SubnetOnlyValidator]] // validationID -> SubnetOnlyValidator inactiveDB database.Database currentStakers *baseStakers @@ -528,8 +541,6 @@ func New( baseDB := versiondb.New(db) - subnetOnlyValidatorsDB := prefixdb.New(SubnetOnlyValidatorsPrefix, baseDB) - validatorsDB := prefixdb.New(ValidatorsPrefix, baseDB) currentValidatorsDB := prefixdb.New(CurrentPrefix, validatorsDB) @@ -544,9 +555,55 @@ func New( pendingSubnetValidatorBaseDB := prefixdb.New(SubnetValidatorPrefix, pendingValidatorsDB) pendingSubnetDelegatorBaseDB := prefixdb.New(SubnetDelegatorPrefix, pendingValidatorsDB) + subnetOnlyValidatorsDB := prefixdb.New(SubnetOnlyPrefix, validatorsDB) + validatorWeightDiffsDB := prefixdb.New(ValidatorWeightDiffsPrefix, validatorsDB) validatorPublicKeyDiffsDB := prefixdb.New(ValidatorPublicKeyDiffsPrefix, validatorsDB) + weightsCache, err := metercacher.New( + "sov_weights_cache", + metricsReg, + cache.NewSizedLRU[ids.ID, uint64](execCfg.L1WeightsCacheSize, func(ids.ID, uint64) int { + return ids.IDLen + wrappers.LongLen + }), + ) + if err != nil { + return nil, err + } + + inactiveSOVsCache, err := metercacher.New( + "sov_inactive_cache", + metricsReg, + cache.NewSizedLRU[ids.ID, maybe.Maybe[SubnetOnlyValidator]]( + execCfg.L1InactiveValidatorsCacheSize, + func(_ ids.ID, maybeSOV maybe.Maybe[SubnetOnlyValidator]) int { + const sovOverhead = ids.IDLen + ids.NodeIDLen + 4*wrappers.LongLen + 3*constants.PointerOverhead + const maybeSOVOverhead = wrappers.BoolLen + sovOverhead + const overhead = ids.IDLen + maybeSOVOverhead + if maybeSOV.IsNothing() { + return overhead + } + + sov := maybeSOV.Value() + return overhead + len(sov.PublicKey) + len(sov.RemainingBalanceOwner) + len(sov.DeactivationOwner) + }, + ), + ) + if err != nil { + return nil, err + } + + subnetIDNodeIDCache, err := metercacher.New( + "sov_subnet_id_node_id_cache", + metricsReg, + cache.NewSizedLRU[subnetIDNodeID, bool](execCfg.L1SubnetIDNodeIDCacheSize, func(subnetIDNodeID, bool) int { + return ids.IDLen + ids.NodeIDLen + wrappers.BoolLen + }), + ) + if err != nil { + return nil, err + } + txCache, err := metercacher.New( "tx_cache", metricsReg, @@ -659,9 +716,12 @@ func New( activeSOVs: newActiveSubnetOnlyValidators(), sovDiff: newSubnetOnlyValidatorsDiff(), subnetOnlyValidatorsDB: subnetOnlyValidatorsDB, + weightsCache: weightsCache, weightsDB: prefixdb.New(WeightsPrefix, subnetOnlyValidatorsDB), + subnetIDNodeIDCache: subnetIDNodeIDCache, subnetIDNodeIDDB: prefixdb.New(SubnetIDNodeIDPrefix, subnetOnlyValidatorsDB), activeDB: prefixdb.New(ActivePrefix, subnetOnlyValidatorsDB), + inactiveCache: inactiveSOVsCache, inactiveDB: prefixdb.New(InactivePrefix, subnetOnlyValidatorsDB), currentStakers: newBaseStakers(), @@ -774,7 +834,10 @@ func (s *state) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { return weight, nil } - // TODO: Add caching + if weight, ok := s.weightsCache.Get(subnetID); ok { + return weight, nil + } + return database.WithDefault(database.GetUInt64, s.weightsDB, subnetID[:], 0) } @@ -797,7 +860,13 @@ func (s *state) getPersistedSubnetOnlyValidator(validationID ids.ID) (SubnetOnly return sov, nil } - // TODO: Add caching + if maybeSOV, ok := s.inactiveCache.Get(validationID); ok { + if maybeSOV.IsNothing() { + return SubnetOnlyValidator{}, database.ErrNotFound + } + return maybeSOV.Value(), nil + } + return getSubnetOnlyValidator(s.inactiveDB, validationID) } @@ -810,9 +879,11 @@ func (s *state) HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool subnetID: subnetID, nodeID: nodeID, } - key := subnetIDNodeID.Marshal() + if has, ok := s.subnetIDNodeIDCache.Get(subnetIDNodeID); ok { + return has, nil + } - // TODO: Add caching + key := subnetIDNodeID.Marshal() return s.subnetIDNodeIDDB.Has(key) } @@ -2638,6 +2709,8 @@ func (s *state) writeSubnetOnlyValidators() error { if err != nil { return err } + + s.weightsCache.Put(subnetID, weight) } for validationID, sov := range s.sovDiff.modified { @@ -2646,6 +2719,7 @@ func (s *state) writeSubnetOnlyValidators() error { if s.activeSOVs.delete(validationID) { err = deleteSubnetOnlyValidator(s.activeDB, validationID) } else { + s.inactiveCache.Put(validationID, maybe.Nothing[SubnetOnlyValidator]()) err = deleteSubnetOnlyValidator(s.inactiveDB, validationID) } if err != nil { @@ -2653,12 +2727,15 @@ func (s *state) writeSubnetOnlyValidators() error { } // Update the subnetIDNodeID mapping - subnetIDNodeID := subnetIDNodeID{ - subnetID: sov.SubnetID, - nodeID: sov.NodeID, - } - subnetIDNodeIDKey := subnetIDNodeID.Marshal() - if sov.isDeleted() { + var ( + isDeleted = sov.isDeleted() + subnetIDNodeID = subnetIDNodeID{ + subnetID: sov.SubnetID, + nodeID: sov.NodeID, + } + subnetIDNodeIDKey = subnetIDNodeID.Marshal() + ) + if isDeleted { err = s.subnetIDNodeIDDB.Delete(subnetIDNodeIDKey) } else { err = s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, validationID[:]) @@ -2667,7 +2744,8 @@ func (s *state) writeSubnetOnlyValidators() error { return err } - if sov.isDeleted() { + s.subnetIDNodeIDCache.Put(subnetIDNodeID, !isDeleted) + if isDeleted { continue } @@ -2676,6 +2754,7 @@ func (s *state) writeSubnetOnlyValidators() error { s.activeSOVs.put(sov) err = putSubnetOnlyValidator(s.activeDB, sov) } else { + s.inactiveCache.Put(validationID, maybe.Some(sov)) err = putSubnetOnlyValidator(s.inactiveDB, sov) } if err != nil { From 9993b05f81e521e1b9ea6185eb66024e4e47e6e1 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Oct 2024 11:07:01 -0400 Subject: [PATCH 255/400] nit --- vms/platformvm/state/state.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index b059eeb4595e..1abc1e227b1e 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -577,9 +577,11 @@ func New( cache.NewSizedLRU[ids.ID, maybe.Maybe[SubnetOnlyValidator]]( execCfg.L1InactiveValidatorsCacheSize, func(_ ids.ID, maybeSOV maybe.Maybe[SubnetOnlyValidator]) int { - const sovOverhead = ids.IDLen + ids.NodeIDLen + 4*wrappers.LongLen + 3*constants.PointerOverhead - const maybeSOVOverhead = wrappers.BoolLen + sovOverhead - const overhead = ids.IDLen + maybeSOVOverhead + const ( + sovOverhead = ids.IDLen + ids.NodeIDLen + 4*wrappers.LongLen + 3*constants.PointerOverhead + maybeSOVOverhead = wrappers.BoolLen + sovOverhead + overhead = ids.IDLen + maybeSOVOverhead + ) if maybeSOV.IsNothing() { return overhead } From 547d426c08f399ba2f2a4534aac5bda8de67e301 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Oct 2024 11:07:32 -0400 Subject: [PATCH 256/400] nit --- vms/platformvm/state/state.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 1abc1e227b1e..cdb4f9c59483 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -580,14 +580,14 @@ func New( const ( sovOverhead = ids.IDLen + ids.NodeIDLen + 4*wrappers.LongLen + 3*constants.PointerOverhead maybeSOVOverhead = wrappers.BoolLen + sovOverhead - overhead = ids.IDLen + maybeSOVOverhead + entryOverhead = ids.IDLen + maybeSOVOverhead ) if maybeSOV.IsNothing() { - return overhead + return entryOverhead } sov := maybeSOV.Value() - return overhead + len(sov.PublicKey) + len(sov.RemainingBalanceOwner) + len(sov.DeactivationOwner) + return entryOverhead + len(sov.PublicKey) + len(sov.RemainingBalanceOwner) + len(sov.DeactivationOwner) }, ), ) From 6bcc0ead771e14c0d7108fdc3728bf040d675da9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Oct 2024 11:56:42 -0400 Subject: [PATCH 257/400] Add TODOs --- vms/platformvm/state/state.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index cdb4f9c59483..14c3f5ff0f7d 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1941,12 +1941,14 @@ func (s *state) initValidatorSets() error { return errValidatorSetAlreadyPopulated } - // Load ACP77 validators + // Load active ACP-77 validators if err := s.activeSOVs.addStakers(s.validators); err != nil { return err } - // Load inactive weights + // Load inactive ACP-77 validator weights + // + // TODO: L1s with no active weight should not be held in memory. it := s.weightsDB.NewIterator() defer it.Release() @@ -2309,6 +2311,8 @@ func (s *state) getInheritedPublicKey(nodeID ids.NodeID) (*bls.PublicKey, error) // // This function must be called prior to writeCurrentStakers and // writeSubnetOnlyValidators. +// +// TODO: L1s with no active weight should not be held in memory. func (s *state) updateValidatorManager(updateValidators bool) error { if !updateValidators { return nil From a7792c738b496250e92f619bca306d6f87b7025c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Oct 2024 12:21:48 -0400 Subject: [PATCH 258/400] Add config changes to readme --- RELEASES.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 16b371b67a12..d5c4b453189e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,14 @@ # Release Notes +## Pending Release + +### Configs + +- Added P-chain configs + - `"l1-weights-cache-size"` + - `"l1-inactive-validators-cache-size"` + - `"l1-subnet-id-node-id-cache-size"` + ## [v1.11.11](https://github.com/ava-labs/avalanchego/releases/tag/v1.11.11) This version is backwards compatible to [v1.11.0](https://github.com/ava-labs/avalanchego/releases/tag/v1.11.0). It is optional, but encouraged. From 4723c46bdba39a88ce02d8253cbbe045df21f888 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Oct 2024 13:00:11 -0400 Subject: [PATCH 259/400] Improve doc for PutSubnetOnlyValidator --- vms/platformvm/state/subnet_only_validator.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index a299cd478c3e..642b081800f0 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -51,13 +51,21 @@ type SubnetOnlyValidators interface { // exists. HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool, error) - // PutSubnetOnlyValidator inserts [sov] as a validator. + // PutSubnetOnlyValidator inserts [sov] as a validator. If the weight of the + // validator is 0, the validator is removed. // // 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. + // If inserting this validator would cause the total weight of subnet only + // validators on a subnet to overflow MaxUint64, an error will be returned. + // + // If inserting this validator would cause there to be multiple validators + // with the same subnetID and nodeID pair to exist at the same time, an + // error will be returned. + // + // If an SoV with the same validationID as a previously removed SoV is + // added, the behavior is undefined. PutSubnetOnlyValidator(sov SubnetOnlyValidator) error } From f1ca6e6206e4dc01c7f0f2f5967aa06b7aad907e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Oct 2024 13:17:19 -0400 Subject: [PATCH 260/400] Add test that decreases weight --- vms/platformvm/state/state_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index df6e92c7b088..a1a8feb6676e 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1665,6 +1665,29 @@ func TestSubnetOnlyValidators(t *testing.T) { }, }, }, + { + name: "decrease active weight", + initial: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 2, // Not removed + EndAccumulatedFee: 1, // Active + }, + }, + sovs: []SubnetOnlyValidator{ + { + ValidationID: sov.ValidationID, + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: pkBytes, + Weight: 1, // Decreased + EndAccumulatedFee: 1, // Active + }, + }, + }, { name: "deactivate", initial: []SubnetOnlyValidator{ From 71f88e87f9c0d78b47e5cd72125a5732eaedb53b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Oct 2024 13:42:16 -0400 Subject: [PATCH 261/400] Fix regression --- vms/platformvm/state/state.go | 111 ++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 37 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 14c3f5ff0f7d..7f136ea49bef 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2364,40 +2364,58 @@ func (s *state) updateValidatorManager(updateValidators bool) error { } } + // Remove all deleted SoV validators. This must be done before adding new + // SoV validators to support the case where a validator is removed and then + // immediately re-added with a different validationID. for validationID, sov := range s.sovDiff.modified { + if !sov.isDeleted() { + 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 + } + + if err := s.validators.RemoveWeight(priorSOV.SubnetID, priorSOV.effectiveNodeID(), priorSOV.Weight); err != nil { + return err + } + } + + // Now the removed SoV validators have been deleted, perform additions and + // modifications. + for validationID, sov := range s.sovDiff.modified { + if sov.isDeleted() { + continue + } + priorSOV, err := s.getPersistedSubnetOnlyValidator(validationID) switch err { case nil: - if sov.isDeleted() { - // Removing a validator - err = s.validators.RemoveWeight(priorSOV.SubnetID, priorSOV.effectiveNodeID(), priorSOV.Weight) - } else { - // Modifying a validator - if priorSOV.isActive() == sov.isActive() { - // This validator's active status isn't changing. This means - // the effectiveNodeIDs are equal. - nodeID := sov.effectiveNodeID() - 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) - } - } else { - // This validator's active status is changing. - err = errors.Join( - s.validators.RemoveWeight(sov.SubnetID, priorSOV.effectiveNodeID(), priorSOV.Weight), - addSoVToValidatorManager(s.validators, sov), - ) + // Modifying an existing validator + if priorSOV.isActive() == sov.isActive() { + // This validator's active status isn't changing. This means + // the effectiveNodeIDs are equal. + nodeID := sov.effectiveNodeID() + 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) } + } else { + // This validator's active status is changing. + err = errors.Join( + s.validators.RemoveWeight(sov.SubnetID, priorSOV.effectiveNodeID(), priorSOV.Weight), + addSoVToValidatorManager(s.validators, sov), + ) } case database.ErrNotFound: - if sov.isDeleted() { - // Deleting a non-existent validator is a noop. This can happen - // if the validator was added and then immediately removed. - continue - } - - // Adding a validator + // Adding a new validator err = addSoVToValidatorManager(s.validators, sov) } if err != nil { @@ -2719,6 +2737,10 @@ func (s *state) writeSubnetOnlyValidators() error { s.weightsCache.Put(subnetID, weight) } + // The SoV diff application is split into two loops to ensure that all + // deletions to the subnetIDNodeIDDB happen prior to any additions. + // Otherwise replacing an SoV by deleting it and then re-adding it with a + // different validationID could result in an inconsistent state. for validationID, sov := range s.sovDiff.modified { // Delete the prior validator if it exists var err error @@ -2732,30 +2754,45 @@ func (s *state) writeSubnetOnlyValidators() error { return err } - // Update the subnetIDNodeID mapping + if !sov.isDeleted() { + continue + } + var ( - isDeleted = sov.isDeleted() subnetIDNodeID = subnetIDNodeID{ subnetID: sov.SubnetID, nodeID: sov.NodeID, } subnetIDNodeIDKey = subnetIDNodeID.Marshal() ) - if isDeleted { - err = s.subnetIDNodeIDDB.Delete(subnetIDNodeIDKey) - } else { - err = s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, validationID[:]) - } - if err != nil { + if err := s.subnetIDNodeIDDB.Delete(subnetIDNodeIDKey); err != nil { return err } - s.subnetIDNodeIDCache.Put(subnetIDNodeID, !isDeleted) - if isDeleted { + s.subnetIDNodeIDCache.Put(subnetIDNodeID, false) + } + + for validationID, sov := range s.sovDiff.modified { + if sov.isDeleted() { continue } + // Update the subnetIDNodeID mapping + var ( + subnetIDNodeID = subnetIDNodeID{ + subnetID: sov.SubnetID, + nodeID: sov.NodeID, + } + subnetIDNodeIDKey = subnetIDNodeID.Marshal() + ) + if err := s.subnetIDNodeIDDB.Put(subnetIDNodeIDKey, validationID[:]); err != nil { + return err + } + + s.subnetIDNodeIDCache.Put(subnetIDNodeID, true) + // Add the new validator + var err error if sov.isActive() { s.activeSOVs.put(sov) err = putSubnetOnlyValidator(s.activeDB, sov) From c2ffd1733eb4b0f4ad4bd16ed026cb4831f2b946 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Oct 2024 13:43:29 -0400 Subject: [PATCH 262/400] nit --- vms/platformvm/state/state.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 7f136ea49bef..a5d39ff0079d 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2387,8 +2387,8 @@ func (s *state) updateValidatorManager(updateValidators bool) error { } } - // Now the removed SoV validators have been deleted, perform additions and - // modifications. + // Now that the removed SoV validators have been deleted, perform additions + // and modifications. for validationID, sov := range s.sovDiff.modified { if sov.isDeleted() { continue From 91a6465a027a84db85ea634a398df9283ba30866 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Oct 2024 14:56:27 -0400 Subject: [PATCH 263/400] nit --- vms/platformvm/block/executor/verifier.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index 6616fd58bad3..d79f1d32b298 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -17,8 +17,8 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/status" "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" + txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) @@ -390,7 +390,7 @@ func (v *verifier) proposalBlock( onDecisionState state.Diff, onCommitState state.Diff, onAbortState state.Diff, - feeCalculator fee.Calculator, + feeCalculator txfee.Calculator, inputs set.Set[ids.ID], atomicRequests map[ids.ID]*atomic.Requests, onAcceptFunc func(), @@ -441,7 +441,7 @@ func (v *verifier) proposalBlock( func (v *verifier) standardBlock( b block.Block, txs []*txs.Tx, - feeCalculator fee.Calculator, + feeCalculator txfee.Calculator, onAcceptState state.Diff, ) error { inputs, atomicRequests, onAcceptFunc, err := v.processStandardTxs( @@ -471,7 +471,7 @@ func (v *verifier) standardBlock( return nil } -func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator fee.Calculator, diff state.Diff, parentID ids.ID) ( +func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator txfee.Calculator, diff state.Diff, parentID ids.ID) ( set.Set[ids.ID], map[ids.ID]*atomic.Requests, func(), @@ -483,7 +483,7 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator fee.Calculato if isEtna { var blockComplexity gas.Dimensions for _, tx := range txs { - txComplexity, err := fee.TxComplexity(tx.Unsigned) + txComplexity, err := txfee.TxComplexity(tx.Unsigned) if err != nil { txID := tx.ID() v.MarkDropped(txID, err) From 07370a5b489be965816689abeb061aadad2d29f8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Oct 2024 15:19:31 -0400 Subject: [PATCH 264/400] nits --- vms/platformvm/state/chain_time_helpers.go | 26 +++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/vms/platformvm/state/chain_time_helpers.go b/vms/platformvm/state/chain_time_helpers.go index 97c2da561363..17d466bcea40 100644 --- a/vms/platformvm/state/chain_time_helpers.go +++ b/vms/platformvm/state/chain_time_helpers.go @@ -49,12 +49,12 @@ func NextBlockTime( } // 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. +// or removed to/from the validator set. If the next staker change time is +// further in the future than [nextTime], then [nextTime] is returned. func GetNextStakerChangeTime( config validatorfee.Config, state Chain, - defaultTime time.Time, + nextTime time.Time, ) (time.Time, error) { currentIterator, err := state.GetCurrentStakerIterator() if err != nil { @@ -75,8 +75,8 @@ func GetNextStakerChangeTime( } time := it.Value().NextTime - if time.Before(defaultTime) { - defaultTime = time + if time.Before(nextTime) { + nextTime = time } } @@ -88,12 +88,11 @@ func GetNextStakerChangeTime( // If there are no SoVs, return if !sovIterator.Next() { - return defaultTime, nil + return nextTime, nil } + // Calculate the remaining funds that the next validator to evict has. var ( - currentTime = state.GetTimestamp() - maxSeconds = uint64(defaultTime.Sub(currentTime) / time.Second) sov = sovIterator.Value() accruedFees = state.GetAccruedFees() ) @@ -102,6 +101,11 @@ func GetNextStakerChangeTime( return time.Time{}, err } + // Calculate how many seconds the remaining funds can last for. + var ( + currentTime = state.GetTimestamp() + maxSeconds = uint64(nextTime.Sub(currentTime) / time.Second) + ) feeState := validatorfee.State{ Current: gas.Gas(state.NumActiveSubnetOnlyValidators()), Excess: state.GetSoVExcess(), @@ -113,10 +117,10 @@ func GetNextStakerChangeTime( ) deactivationTime := currentTime.Add(time.Duration(remainingSeconds) * time.Second) - if deactivationTime.Before(defaultTime) { - defaultTime = deactivationTime + if deactivationTime.Before(nextTime) { + nextTime = deactivationTime } - return defaultTime, nil + return nextTime, nil } // PickFeeCalculator creates either a static or a dynamic fee calculator, From 23aff43b8cd7664022e2b0f08f7c6a47a6e8a965 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 31 Oct 2024 15:24:42 -0400 Subject: [PATCH 265/400] Add additional test --- vms/platformvm/state/chain_time_helpers_test.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/vms/platformvm/state/chain_time_helpers_test.go b/vms/platformvm/state/chain_time_helpers_test.go index d93ab1bfd48c..32d11d1f7719 100644 --- a/vms/platformvm/state/chain_time_helpers_test.go +++ b/vms/platformvm/state/chain_time_helpers_test.go @@ -15,6 +15,7 @@ import ( "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/utils/units" "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" @@ -113,7 +114,7 @@ func TestGetNextStakerChangeTime(t *testing.T) { expected: genesistest.DefaultValidatorStartTime.Add(time.Second), }, { - name: "current and subnet only validators", + name: "current and subnet only validator with low balance", sovs: []SubnetOnlyValidator{ { ValidationID: ids.GenerateTestID(), @@ -126,6 +127,20 @@ func TestGetNextStakerChangeTime(t *testing.T) { maxTime: mockable.MaxTime, expected: genesistest.DefaultValidatorStartTime.Add(time.Second), }, + { + name: "current and subnet only validator with high balance", + sovs: []SubnetOnlyValidator{ + { + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + Weight: 1, + EndAccumulatedFee: units.Avax, // This validator won't be evicted soon. + }, + }, + maxTime: mockable.MaxTime, + expected: genesistest.DefaultValidatorEndTime, + }, { name: "restricted timestamp", maxTime: genesistest.DefaultValidatorStartTime, From 66011f0069ad4df9462e6bb96f14e8325018d8d0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 1 Nov 2024 13:50:29 -0400 Subject: [PATCH 266/400] Move caching logic --- vms/platformvm/state/state.go | 23 ++-- vms/platformvm/state/subnet_only_validator.go | 45 +++++-- .../state/subnet_only_validator_test.go | 111 ++++++++++++++---- 3 files changed, 135 insertions(+), 44 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index a5d39ff0079d..b7b1341bd23f 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -104,6 +104,8 @@ var ( HeightsIndexedKey = []byte("heights indexed") InitializedKey = []byte("initialized") BlocksReindexedKey = []byte("blocks reindexed") + + emptySoVCache = &cache.Empty[ids.ID, maybe.Maybe[SubnetOnlyValidator]]{} ) // Chain collects all methods to manage the state of the chain for block @@ -862,14 +864,7 @@ func (s *state) getPersistedSubnetOnlyValidator(validationID ids.ID) (SubnetOnly return sov, nil } - if maybeSOV, ok := s.inactiveCache.Get(validationID); ok { - if maybeSOV.IsNothing() { - return SubnetOnlyValidator{}, database.ErrNotFound - } - return maybeSOV.Value(), nil - } - - return getSubnetOnlyValidator(s.inactiveDB, validationID) + return getSubnetOnlyValidator(s.inactiveCache, s.inactiveDB, validationID) } func (s *state) HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool, error) { @@ -1942,7 +1937,7 @@ func (s *state) initValidatorSets() error { } // Load active ACP-77 validators - if err := s.activeSOVs.addStakers(s.validators); err != nil { + if err := s.activeSOVs.addStakersToValidatorManager(s.validators); err != nil { return err } @@ -2745,10 +2740,9 @@ func (s *state) writeSubnetOnlyValidators() error { // Delete the prior validator if it exists var err error if s.activeSOVs.delete(validationID) { - err = deleteSubnetOnlyValidator(s.activeDB, validationID) + err = deleteSubnetOnlyValidator(s.activeDB, emptySoVCache, validationID) } else { - s.inactiveCache.Put(validationID, maybe.Nothing[SubnetOnlyValidator]()) - err = deleteSubnetOnlyValidator(s.inactiveDB, validationID) + err = deleteSubnetOnlyValidator(s.inactiveDB, s.inactiveCache, validationID) } if err != nil { return err @@ -2795,10 +2789,9 @@ func (s *state) writeSubnetOnlyValidators() error { var err error if sov.isActive() { s.activeSOVs.put(sov) - err = putSubnetOnlyValidator(s.activeDB, sov) + err = putSubnetOnlyValidator(s.activeDB, emptySoVCache, sov) } else { - s.inactiveCache.Put(validationID, maybe.Some(sov)) - err = putSubnetOnlyValidator(s.inactiveDB, sov) + err = putSubnetOnlyValidator(s.inactiveDB, s.inactiveCache, sov) } if err != nil { return err diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 642b081800f0..556ba3dd27b9 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -10,6 +10,7 @@ import ( "github.com/google/btree" + "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" @@ -17,6 +18,7 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/avalanchego/utils/maybe" "github.com/ava-labs/avalanchego/vms/platformvm/block" ) @@ -184,7 +186,18 @@ func (v SubnetOnlyValidator) effectivePublicKeyBytes() []byte { return nil } -func getSubnetOnlyValidator(db database.KeyValueReader, validationID ids.ID) (SubnetOnlyValidator, error) { +func getSubnetOnlyValidator( + cache cache.Cacher[ids.ID, maybe.Maybe[SubnetOnlyValidator]], + db database.KeyValueReader, + validationID ids.ID, +) (SubnetOnlyValidator, error) { + if maybeSOV, ok := cache.Get(validationID); ok { + if maybeSOV.IsNothing() { + return SubnetOnlyValidator{}, database.ErrNotFound + } + return maybeSOV.Value(), nil + } + bytes, err := db.Get(validationID[:]) if err != nil { return SubnetOnlyValidator{}, err @@ -199,16 +212,34 @@ func getSubnetOnlyValidator(db database.KeyValueReader, validationID ids.ID) (Su return vdr, nil } -func putSubnetOnlyValidator(db database.KeyValueWriter, vdr SubnetOnlyValidator) error { - bytes, err := block.GenesisCodec.Marshal(block.CodecVersion, vdr) +func putSubnetOnlyValidator( + db database.KeyValueWriter, + cache cache.Cacher[ids.ID, maybe.Maybe[SubnetOnlyValidator]], + sov SubnetOnlyValidator, +) error { + bytes, err := block.GenesisCodec.Marshal(block.CodecVersion, sov) if err != nil { return fmt.Errorf("failed to marshal SubnetOnlyValidator: %w", err) } - return db.Put(vdr.ValidationID[:], bytes) + if err := db.Put(sov.ValidationID[:], bytes); err != nil { + return err + } + + cache.Put(sov.ValidationID, maybe.Some(sov)) + return nil } -func deleteSubnetOnlyValidator(db database.KeyValueDeleter, validationID ids.ID) error { - return db.Delete(validationID[:]) +func deleteSubnetOnlyValidator( + db database.KeyValueDeleter, + cache cache.Cacher[ids.ID, maybe.Maybe[SubnetOnlyValidator]], + validationID ids.ID, +) error { + if err := db.Delete(validationID[:]); err != nil { + return err + } + + cache.Put(validationID, maybe.Nothing[SubnetOnlyValidator]()) + return nil } type subnetOnlyValidatorsDiff struct { @@ -368,7 +399,7 @@ func (a *activeSubnetOnlyValidators) newIterator() iterator.Iterator[SubnetOnlyV return iterator.FromTree(a.tree) } -func (a *activeSubnetOnlyValidators) addStakers(vdrs validators.Manager) error { +func (a *activeSubnetOnlyValidators) addStakersToValidatorManager(vdrs validators.Manager) error { for validationID, sov := range a.lookup { pk := bls.PublicKeyFromValidUncompressedBytes(sov.PublicKey) if err := vdrs.AddStaker(sov.SubnetID, sov.NodeID, pk, validationID, sov.Weight); err != nil { diff --git a/vms/platformvm/state/subnet_only_validator_test.go b/vms/platformvm/state/subnet_only_validator_test.go index 6b6c86520a66..b7399eee195b 100644 --- a/vms/platformvm/state/subnet_only_validator_test.go +++ b/vms/platformvm/state/subnet_only_validator_test.go @@ -9,11 +9,13 @@ import ( "github.com/stretchr/testify/require" + "github.com/ava-labs/avalanchego/cache" "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/utils/maybe" "github.com/ava-labs/avalanchego/vms/platformvm/block" "github.com/ava-labs/avalanchego/vms/platformvm/fx" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -139,11 +141,8 @@ func TestSubnetOnlyValidator_constantsAreUnmodified(t *testing.T) { } func TestSubnetOnlyValidator_DatabaseHelpers(t *testing.T) { - require := require.New(t) - db := memdb.New() - sk, err := bls.NewSecretKey() - require.NoError(err) + require.NoError(t, err) pk := bls.PublicFromSecretKey(sk) pkBytes := bls.PublicKeyToUncompressedBytes(pk) @@ -154,7 +153,7 @@ func TestSubnetOnlyValidator_DatabaseHelpers(t *testing.T) { }, } remainingBalanceOwnerBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &remainingBalanceOwner) - require.NoError(err) + require.NoError(t, err) var deactivationOwner fx.Owner = &secp256k1fx.OutputOwners{ Threshold: 1, @@ -163,9 +162,9 @@ func TestSubnetOnlyValidator_DatabaseHelpers(t *testing.T) { }, } deactivationOwnerBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &deactivationOwner) - require.NoError(err) + require.NoError(t, err) - vdr := SubnetOnlyValidator{ + sov := SubnetOnlyValidator{ ValidationID: ids.GenerateTestID(), SubnetID: ids.GenerateTestID(), NodeID: ids.GenerateTestNodeID(), @@ -178,24 +177,92 @@ func TestSubnetOnlyValidator_DatabaseHelpers(t *testing.T) { 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.Zero(gotVdr) + var ( + addedDB = memdb.New() + removedDB = memdb.New() + addedAndRemovedDB = memdb.New() + addedAndRemovedAndAddedDB = memdb.New() + + addedCache = &cache.LRU[ids.ID, maybe.Maybe[SubnetOnlyValidator]]{Size: 10} + removedCache = &cache.LRU[ids.ID, maybe.Maybe[SubnetOnlyValidator]]{Size: 10} + addedAndRemovedCache = &cache.LRU[ids.ID, maybe.Maybe[SubnetOnlyValidator]]{Size: 10} + addedAndRemovedAndAddedCache = &cache.LRU[ids.ID, maybe.Maybe[SubnetOnlyValidator]]{Size: 10} + ) // Place the validator on disk - require.NoError(putSubnetOnlyValidator(db, vdr)) + require.NoError(t, putSubnetOnlyValidator(addedDB, addedCache, sov)) + require.NoError(t, putSubnetOnlyValidator(addedAndRemovedDB, addedAndRemovedCache, sov)) + require.NoError(t, putSubnetOnlyValidator(addedAndRemovedAndAddedDB, addedAndRemovedAndAddedCache, sov)) - // Verify that the validator can be fetched from disk - gotVdr, err = getSubnetOnlyValidator(db, vdr.ValidationID) - require.NoError(err) - require.Equal(vdr, gotVdr) + // Remove the validator on disk + require.NoError(t, deleteSubnetOnlyValidator(removedDB, removedCache, sov.ValidationID)) + require.NoError(t, deleteSubnetOnlyValidator(addedAndRemovedDB, addedAndRemovedCache, sov.ValidationID)) + require.NoError(t, deleteSubnetOnlyValidator(addedAndRemovedAndAddedDB, addedAndRemovedAndAddedCache, sov.ValidationID)) - // Remove the validator from disk - require.NoError(deleteSubnetOnlyValidator(db, vdr.ValidationID)) + // Reintroduce the validator to disk + require.NoError(t, putSubnetOnlyValidator(addedAndRemovedAndAddedDB, addedAndRemovedAndAddedCache, sov)) - // Verify that the validator has been removed from disk - gotVdr, err = getSubnetOnlyValidator(db, vdr.ValidationID) - require.ErrorIs(err, database.ErrNotFound) - require.Zero(gotVdr) + addedTests := []struct { + name string + db database.Database + cache cache.Cacher[ids.ID, maybe.Maybe[SubnetOnlyValidator]] + }{ + { + name: "added in cache", + db: memdb.New(), + cache: addedCache, + }, + { + name: "added on disk", + db: addedDB, + cache: emptySoVCache, + }, + { + name: "added and removed and added in cache", + db: memdb.New(), + cache: addedAndRemovedAndAddedCache, + }, + { + name: "added and removed and added on disk", + db: addedAndRemovedAndAddedDB, + cache: emptySoVCache, + }, + } + for _, test := range addedTests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + gotSOV, err := getSubnetOnlyValidator(test.cache, test.db, sov.ValidationID) + require.NoError(err) + require.Equal(sov, gotSOV) + }) + } + + removedTests := []struct { + name string + db database.Database + cache cache.Cacher[ids.ID, maybe.Maybe[SubnetOnlyValidator]] + }{ + { + name: "empty", + db: memdb.New(), + cache: emptySoVCache, + }, + { + name: "removed from cache", + db: addedDB, + cache: removedCache, + }, + { + name: "removed from disk", + db: removedDB, + cache: emptySoVCache, + }, + } + for _, test := range removedTests { + t.Run(test.name, func(t *testing.T) { + _, err := getSubnetOnlyValidator(test.cache, test.db, sov.ValidationID) + require.ErrorIs(t, err, database.ErrNotFound) + }) + } } From d183148fe949cf9b7cec9e4914f52923964cdee5 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 1 Nov 2024 15:33:57 -0400 Subject: [PATCH 267/400] merged --- vms/platformvm/state/subnet_only_validator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index 8979da0cc9e3..c0a363d4e4a9 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -290,7 +290,7 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state Chain, sov Subne ) switch priorSOV, err := state.GetSubnetOnlyValidator(sov.ValidationID); err { case nil: - if !priorSOV.constantsAreUnmodified(sov) { + if !priorSOV.immutableFieldsAreUnmodified(sov) { return ErrMutatedSubnetOnlyValidator } From b1bb45872d423ddec5e9493dbcb165b27f885209 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 1 Nov 2024 15:36:20 -0400 Subject: [PATCH 268/400] nit --- vms/platformvm/state/state_test.go | 2 +- vms/platformvm/state/subnet_only_validator.go | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index a1a8feb6676e..2b28dcc0977c 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1997,7 +1997,7 @@ func TestSubnetOnlyValidators(t *testing.T) { } // TestLoadSubnetOnlyValidatorAndLegacy tests that the state can be loaded when -// there is a mix of legacy validators and subnet only validators in the same +// there is a mix of legacy validators and subnet-only validators in the same // subnet. func TestLoadSubnetOnlyValidatorAndLegacy(t *testing.T) { var ( diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index c0a363d4e4a9..3f5799e7da28 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -26,22 +26,22 @@ var ( _ btree.LessFunc[SubnetOnlyValidator] = SubnetOnlyValidator.Less _ utils.Sortable[SubnetOnlyValidator] = SubnetOnlyValidator{} - ErrMutatedSubnetOnlyValidator = errors.New("subnet only validator contains mutated constant fields") - ErrConflictingSubnetOnlyValidator = errors.New("subnet only validator contains conflicting subnetID + nodeID pair") - ErrDuplicateSubnetOnlyValidator = errors.New("subnet only validator contains duplicate subnetID + nodeID pair") + ErrMutatedSubnetOnlyValidator = errors.New("subnet-only validator contains mutated constant fields") + ErrConflictingSubnetOnlyValidator = errors.New("subnet-only validator contains conflicting subnetID + nodeID pair") + ErrDuplicateSubnetOnlyValidator = errors.New("subnet-only validator contains duplicate subnetID + nodeID pair") ) type SubnetOnlyValidators interface { // GetActiveSubnetOnlyValidatorsIterator returns an iterator of all the - // active subnet only validators in increasing order of EndAccumulatedFee. + // active subnet-only validators in increasing order of EndAccumulatedFee. GetActiveSubnetOnlyValidatorsIterator() (iterator.Iterator[SubnetOnlyValidator], error) // NumActiveSubnetOnlyValidators returns the number of currently active - // subnet only validators. + // subnet-only validators. NumActiveSubnetOnlyValidators() int // WeightOfSubnetOnlyValidators returns the total active and inactive weight - // of subnet only validators on [subnetID]. + // of subnet-only validators on [subnetID]. WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) // GetSubnetOnlyValidator returns the validator with [validationID] if it @@ -57,9 +57,9 @@ type SubnetOnlyValidators interface { // validator is 0, the validator is removed. // // If inserting this validator attempts to modify any of the constant fields - // of the subnet only validator struct, an error will be returned. + // of the subnet-only validator struct, an error will be returned. // - // If inserting this validator would cause the total weight of subnet only + // If inserting this validator would cause the total weight of subnet-only // validators on a subnet to overflow MaxUint64, an error will be returned. // // If inserting this validator would cause there to be multiple validators From ad31107c4501d4c0a264e87bf31f5c6c81029962 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 1 Nov 2024 15:59:41 -0400 Subject: [PATCH 269/400] Add weight diff helpers --- vms/platformvm/state/stakers.go | 4 +- vms/platformvm/state/state.go | 10 ++- vms/platformvm/state/state_test.go | 111 ++++++++++------------------- 3 files changed, 48 insertions(+), 77 deletions(-) diff --git a/vms/platformvm/state/stakers.go b/vms/platformvm/state/stakers.go index 14e4dcf7b1ef..f076e23ab59b 100644 --- a/vms/platformvm/state/stakers.go +++ b/vms/platformvm/state/stakers.go @@ -283,7 +283,7 @@ func (d *diffValidator) WeightDiff() (ValidatorWeightDiff, error) { } for _, staker := range d.deletedDelegators { - if err := weightDiff.Add(true, staker.Weight); err != nil { + if err := weightDiff.Sub(staker.Weight); err != nil { return ValidatorWeightDiff{}, fmt.Errorf("failed to decrease node weight diff: %w", err) } } @@ -294,7 +294,7 @@ func (d *diffValidator) WeightDiff() (ValidatorWeightDiff, error) { for addedDelegatorIterator.Next() { staker := addedDelegatorIterator.Value() - if err := weightDiff.Add(false, staker.Weight); err != nil { + if err := weightDiff.Add(staker.Weight); err != nil { return ValidatorWeightDiff{}, fmt.Errorf("failed to increase node weight diff: %w", err) } } diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 09a22f8c27de..3368884da910 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -425,7 +425,15 @@ type ValidatorWeightDiff struct { Amount uint64 `serialize:"true"` } -func (v *ValidatorWeightDiff) Add(negative bool, amount uint64) error { +func (v *ValidatorWeightDiff) Add(amount uint64) error { + return v.add(false, amount) +} + +func (v *ValidatorWeightDiff) Sub(amount uint64) error { + return v.add(true, amount) +} + +func (v *ValidatorWeightDiff) add(negative bool, amount uint64) error { if v.Decrease == negative { var err error v.Amount, err = safemath.Add(v.Amount, amount) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 143f673b4e0f..f56204d1f208 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -671,29 +671,32 @@ func createPermissionlessDelegatorTx(subnetID ids.ID, delegatorData txs.Validato } func TestValidatorWeightDiff(t *testing.T) { + type op struct { + op func(*ValidatorWeightDiff, uint64) error + amount uint64 + } type test struct { name string - ops []func(*ValidatorWeightDiff) error + ops []op expected *ValidatorWeightDiff expectedErr error } + var ( + add = (*ValidatorWeightDiff).Add + sub = (*ValidatorWeightDiff).Sub + ) tests := []test{ { name: "no ops", - ops: []func(*ValidatorWeightDiff) error{}, expected: &ValidatorWeightDiff{}, expectedErr: nil, }, { name: "simple decrease", - ops: []func(*ValidatorWeightDiff) error{ - func(d *ValidatorWeightDiff) error { - return d.Add(true, 1) - }, - func(d *ValidatorWeightDiff) error { - return d.Add(true, 1) - }, + ops: []op{ + {sub, 1}, + {sub, 1}, }, expected: &ValidatorWeightDiff{ Decrease: true, @@ -703,26 +706,17 @@ func TestValidatorWeightDiff(t *testing.T) { }, { name: "decrease overflow", - ops: []func(*ValidatorWeightDiff) error{ - func(d *ValidatorWeightDiff) error { - return d.Add(true, math.MaxUint64) - }, - func(d *ValidatorWeightDiff) error { - return d.Add(true, 1) - }, + ops: []op{ + {sub, math.MaxUint64}, + {sub, 1}, }, - expected: &ValidatorWeightDiff{}, expectedErr: safemath.ErrOverflow, }, { name: "simple increase", - ops: []func(*ValidatorWeightDiff) error{ - func(d *ValidatorWeightDiff) error { - return d.Add(false, 1) - }, - func(d *ValidatorWeightDiff) error { - return d.Add(false, 1) - }, + ops: []op{ + {add, 1}, + {add, 1}, }, expected: &ValidatorWeightDiff{ Decrease: false, @@ -732,58 +726,24 @@ func TestValidatorWeightDiff(t *testing.T) { }, { name: "increase overflow", - ops: []func(*ValidatorWeightDiff) error{ - func(d *ValidatorWeightDiff) error { - return d.Add(false, math.MaxUint64) - }, - func(d *ValidatorWeightDiff) error { - return d.Add(false, 1) - }, + ops: []op{ + {add, math.MaxUint64}, + {add, 1}, }, - expected: &ValidatorWeightDiff{}, expectedErr: safemath.ErrOverflow, }, { name: "varied use", - ops: []func(*ValidatorWeightDiff) error{ - // Add to 0 - func(d *ValidatorWeightDiff) error { - return d.Add(false, 2) // Value 2 - }, - // Subtract from positive number - func(d *ValidatorWeightDiff) error { - return d.Add(true, 1) // Value 1 - }, - // Subtract from positive number - // to make it negative - func(d *ValidatorWeightDiff) error { - return d.Add(true, 3) // Value -2 - }, - // Subtract from a negative number - func(d *ValidatorWeightDiff) error { - return d.Add(true, 3) // Value -5 - }, - // Add to a negative number - func(d *ValidatorWeightDiff) error { - return d.Add(false, 1) // Value -4 - }, - // Add to a negative number - // to make it positive - func(d *ValidatorWeightDiff) error { - return d.Add(false, 5) // Value 1 - }, - // Add to a positive number - func(d *ValidatorWeightDiff) error { - return d.Add(false, 1) // Value 2 - }, - // Get to zero - func(d *ValidatorWeightDiff) error { - return d.Add(true, 2) // Value 0 - }, - // Subtract from zero - func(d *ValidatorWeightDiff) error { - return d.Add(true, 2) // Value -2 - }, + ops: []op{ + {add, 2}, // = 2 + {sub, 1}, // = 1 + {sub, 3}, // = -2 + {sub, 3}, // = -5 + {add, 1}, // = -4 + {add, 5}, // = 1 + {add, 1}, // = 2 + {sub, 2}, // = 0 + {sub, 2}, // = -2 }, expected: &ValidatorWeightDiff{ Decrease: true, @@ -796,10 +756,13 @@ func TestValidatorWeightDiff(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require := require.New(t) - diff := &ValidatorWeightDiff{} - errs := wrappers.Errs{} + + var ( + diff = &ValidatorWeightDiff{} + errs = wrappers.Errs{} + ) for _, op := range tt.ops { - errs.Add(op(diff)) + errs.Add(op.op(diff, op.amount)) } require.ErrorIs(errs.Err, tt.expectedErr) if tt.expectedErr != nil { From cee236e010c43d4e1ffdfef2552065a55423556a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 1 Nov 2024 16:47:48 -0400 Subject: [PATCH 270/400] nit --- vms/platformvm/state/state_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index f56204d1f208..60b92c58dc44 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -688,9 +688,8 @@ func TestValidatorWeightDiff(t *testing.T) { ) tests := []test{ { - name: "no ops", - expected: &ValidatorWeightDiff{}, - expectedErr: nil, + name: "no ops", + expected: &ValidatorWeightDiff{}, }, { name: "simple decrease", @@ -702,7 +701,6 @@ func TestValidatorWeightDiff(t *testing.T) { Decrease: true, Amount: 2, }, - expectedErr: nil, }, { name: "decrease overflow", @@ -722,7 +720,6 @@ func TestValidatorWeightDiff(t *testing.T) { Decrease: false, Amount: 2, }, - expectedErr: nil, }, { name: "increase overflow", @@ -749,7 +746,6 @@ func TestValidatorWeightDiff(t *testing.T) { Decrease: true, Amount: 2, }, - expectedErr: nil, }, } From 900eba34150de4285c59a71c757a13644b2d8e37 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 1 Nov 2024 16:51:57 -0400 Subject: [PATCH 271/400] add -> addOrSub --- vms/platformvm/state/state.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 3368884da910..1e45b6f07826 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -426,15 +426,15 @@ type ValidatorWeightDiff struct { } func (v *ValidatorWeightDiff) Add(amount uint64) error { - return v.add(false, amount) + return v.addOrSub(false, amount) } func (v *ValidatorWeightDiff) Sub(amount uint64) error { - return v.add(true, amount) + return v.addOrSub(true, amount) } -func (v *ValidatorWeightDiff) add(negative bool, amount uint64) error { - if v.Decrease == negative { +func (v *ValidatorWeightDiff) addOrSub(sub bool, amount uint64) error { + if v.Decrease == sub { var err error v.Amount, err = safemath.Add(v.Amount, amount) return err @@ -444,7 +444,7 @@ func (v *ValidatorWeightDiff) add(negative bool, amount uint64) error { v.Amount -= amount } else { v.Amount = safemath.AbsDiff(v.Amount, amount) - v.Decrease = negative + v.Decrease = sub } return nil } From 7cbf31b3f759f20235838146a705a90ae0a3df40 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 1 Nov 2024 20:00:29 -0400 Subject: [PATCH 272/400] fix merge --- vms/platformvm/state/state.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 07dce8b248a6..ad09f28457b7 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2496,7 +2496,7 @@ func (s *state) calculateValidatorDiffs() (map[subnetIDNodeID]*validatorDiff, er nodeID: priorSOV.effectiveNodeID(), } diff := getOrDefault(changes, subnetIDNodeID) - if err := diff.weightDiff.Add(true, priorSOV.Weight); err != nil { + if err := diff.weightDiff.Sub(priorSOV.Weight); err != nil { return nil, err } diff.prevPublicKey = priorSOV.effectivePublicKeyBytes() @@ -2516,7 +2516,7 @@ func (s *state) calculateValidatorDiffs() (map[subnetIDNodeID]*validatorDiff, er nodeID: sov.effectiveNodeID(), } diff := getOrDefault(changes, subnetIDNodeID) - if err := diff.weightDiff.Add(false, sov.Weight); err != nil { + if err := diff.weightDiff.Add(sov.Weight); err != nil { return nil, err } diff.newPublicKey = sov.effectivePublicKeyBytes() From 33d297d236960e0b335a8be7f1daedd16db7368c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 2 Nov 2024 12:44:02 -0400 Subject: [PATCH 273/400] Add test --- vms/platformvm/block/executor/verifier.go | 2 +- .../block/executor/verifier_test.go | 184 +++++++++++++++++- 2 files changed, 180 insertions(+), 6 deletions(-) diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index d79f1d32b298..56c030c05366 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -563,11 +563,11 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator txfee.Calcula // This ensures that SoVs are not undercharged for the next second. if isEtna { var ( + accruedFees = diff.GetAccruedFees() validatorFeeState = validatorfee.State{ Current: gas.Gas(diff.NumActiveSubnetOnlyValidators()), Excess: diff.GetSoVExcess(), } - accruedFees = diff.GetAccruedFees() potentialCost = validatorFeeState.CostOf( v.txExecutorBackend.Config.ValidatorFeeConfig, 1, // 1 second diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 91015bdcd95e..141792177eb7 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -21,9 +21,12 @@ import ( "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/timer/mockable" + "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/components/verify" @@ -35,13 +38,15 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/status" "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" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool/mempoolmock" "github.com/ava-labs/avalanchego/vms/platformvm/txs/txsmock" "github.com/ava-labs/avalanchego/vms/platformvm/txs/txstest" "github.com/ava-labs/avalanchego/vms/platformvm/utxo" "github.com/ava-labs/avalanchego/vms/secp256k1fx" + + txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) func newTestVerifier(t testing.TB, s state.State) *verifier { @@ -71,9 +76,15 @@ func newTestVerifier(t testing.TB, s state.State) *verifier { }, txExecutorBackend: &executor.Backend{ Config: &config.Config{ - CreateAssetTxFee: genesis.LocalParams.CreateAssetTxFee, - StaticFeeConfig: genesis.LocalParams.StaticFeeConfig, - DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, + CreateAssetTxFee: genesis.LocalParams.CreateAssetTxFee, + StaticFeeConfig: genesis.LocalParams.StaticFeeConfig, + DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, + ValidatorFeeConfig: validatorfee.Config{ + Capacity: genesis.LocalParams.ValidatorFeeConfig.Capacity, + Target: genesis.LocalParams.ValidatorFeeConfig.Target, + MinPrice: gas.Price(2 * units.NanoAvax), + ExcessConversionConstant: genesis.LocalParams.ValidatorFeeConfig.ExcessConversionConstant, + }, SybilProtectionEnabled: true, UpgradeConfig: upgrades, }, @@ -1134,7 +1145,7 @@ func TestBlockExecutionWithComplexity(t *testing.T) { baseTx1, err := wallet.IssueBaseTx([]*avax.TransferableOutput{}) require.NoError(t, err) - blockComplexity, err := fee.TxComplexity(baseTx0.Unsigned, baseTx1.Unsigned) + blockComplexity, err := txfee.TxComplexity(baseTx0.Unsigned, baseTx1.Unsigned) require.NoError(t, err) blockGas, err := blockComplexity.ToGas(verifier.txExecutorBackend.Config.DynamicFeeConfig.Weights) require.NoError(t, err) @@ -1204,3 +1215,166 @@ func TestBlockExecutionWithComplexity(t *testing.T) { }) } } + +func TestBlockExecutionEvictsLowBalanceSoVs(t *testing.T) { + sk, err := bls.NewSecretKey() + require.NoError(t, err) + + var ( + pk = bls.PublicFromSecretKey(sk) + pkBytes = bls.PublicKeyToUncompressedBytes(pk) + + fractionalTimeSoV0 = state.SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: pkBytes, + Weight: 1, + EndAccumulatedFee: 5 * units.NanoAvax, // lasts 2.5 seconds + } + fractionalTimeSoV1 = state.SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: pkBytes, + Weight: 1, + EndAccumulatedFee: 5 * units.NanoAvax, // lasts 2.5 seconds + } + fractionalSoVEvictedTime = genesistest.DefaultValidatorStartTime.Add(2 * time.Second) // evicts early rather than late + + wholeTimeSoV = state.SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: pkBytes, + Weight: 1, + EndAccumulatedFee: 8 * units.NanoAvax, // lasts 4 seconds + } + wholeSoVEvictedTime = genesistest.DefaultValidatorStartTime.Add(4 * time.Second) // evicts on time + ) + + tests := []struct { + name string + initialSoVs []state.SubnetOnlyValidator + timestamp time.Time + expectedErr error + expectedSoVs []state.SubnetOnlyValidator + }{ + { + name: "no SoVs", + timestamp: genesistest.DefaultValidatorStartTime.Add(10 * time.Second), + }, + { + name: "fractional SoVs are not overcharged", + initialSoVs: []state.SubnetOnlyValidator{ + fractionalTimeSoV0, + }, + timestamp: fractionalSoVEvictedTime.Add(-time.Second), + expectedSoVs: []state.SubnetOnlyValidator{ + fractionalTimeSoV0, + }, + }, + { + name: "fractional SoVs are not undercharged", + initialSoVs: []state.SubnetOnlyValidator{ + fractionalTimeSoV0, + fractionalTimeSoV1, + }, + timestamp: fractionalSoVEvictedTime, + }, + { + name: "whole SoVs are not overcharged", + initialSoVs: []state.SubnetOnlyValidator{ + wholeTimeSoV, + }, + timestamp: wholeSoVEvictedTime.Add(-time.Second), + expectedSoVs: []state.SubnetOnlyValidator{ + wholeTimeSoV, + }, + }, + { + name: "whole SoVs are not undercharged", + initialSoVs: []state.SubnetOnlyValidator{ + wholeTimeSoV, + }, + timestamp: wholeSoVEvictedTime, + }, + { + name: "partial eviction", + initialSoVs: []state.SubnetOnlyValidator{ + fractionalTimeSoV0, + wholeTimeSoV, + }, + timestamp: fractionalSoVEvictedTime, + expectedSoVs: []state.SubnetOnlyValidator{ + wholeTimeSoV, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + s := statetest.New(t, statetest.Config{}) + for _, sov := range test.initialSoVs { + require.NoError(s.PutSubnetOnlyValidator(sov)) + } + + verifier := newTestVerifier(t, s) + verifier.txExecutorBackend.Clk.Set(test.timestamp) + + wallet := txstest.NewWallet( + t, + verifier.ctx, + verifier.txExecutorBackend.Config, + s, + secp256k1fx.NewKeychain(genesis.EWOQKey), + nil, // subnetIDs + nil, // chainIDs + ) + + baseTx, err := wallet.IssueBaseTx([]*avax.TransferableOutput{}) + require.NoError(err) + + timestamp, _, err := state.NextBlockTime( + verifier.txExecutorBackend.Config.ValidatorFeeConfig, + s, + verifier.txExecutorBackend.Clk, + ) + require.NoError(err) + + lastAcceptedID := s.GetLastAccepted() + lastAccepted, err := s.GetStatelessBlock(lastAcceptedID) + require.NoError(err) + + blk, err := block.NewBanffStandardBlock( + timestamp, + lastAcceptedID, + lastAccepted.Height()+1, + []*txs.Tx{ + baseTx, + }, + ) + require.NoError(err) + + blkID := blk.ID() + err = blk.Visit(verifier) + require.ErrorIs(err, test.expectedErr) + if err != nil { + require.NotContains(verifier.blkIDToState, blkID) + return + } + + require.Contains(verifier.blkIDToState, blkID) + blockState := verifier.blkIDToState[blkID] + require.Equal(blk, blockState.statelessBlock) + + sovs, err := blockState.onAcceptState.GetActiveSubnetOnlyValidatorsIterator() + require.NoError(err) + require.Equal( + test.expectedSoVs, + iterator.ToSlice(sovs), + ) + }) + } +} From 486f732a9ee0b04aa075c386e090f76fec8493e2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 2 Nov 2024 12:45:51 -0400 Subject: [PATCH 274/400] nit --- vms/platformvm/block/executor/verifier_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 141792177eb7..69833a670d94 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -82,7 +82,7 @@ func newTestVerifier(t testing.TB, s state.State) *verifier { ValidatorFeeConfig: validatorfee.Config{ Capacity: genesis.LocalParams.ValidatorFeeConfig.Capacity, Target: genesis.LocalParams.ValidatorFeeConfig.Target, - MinPrice: gas.Price(2 * units.NanoAvax), + MinPrice: gas.Price(2 * units.NanoAvax), // Min price is increased to allow fractional fees ExcessConversionConstant: genesis.LocalParams.ValidatorFeeConfig.ExcessConversionConstant, }, SybilProtectionEnabled: true, From b448a042a285835b170752a576eefdd465be4aec Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 2 Nov 2024 12:47:37 -0400 Subject: [PATCH 275/400] reduce diff --- vms/platformvm/block/executor/verifier_test.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 69833a670d94..97a1c2ca1e51 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -1257,7 +1257,6 @@ func TestBlockExecutionEvictsLowBalanceSoVs(t *testing.T) { name string initialSoVs []state.SubnetOnlyValidator timestamp time.Time - expectedErr error expectedSoVs []state.SubnetOnlyValidator }{ { @@ -1357,14 +1356,9 @@ func TestBlockExecutionEvictsLowBalanceSoVs(t *testing.T) { ) require.NoError(err) - blkID := blk.ID() - err = blk.Visit(verifier) - require.ErrorIs(err, test.expectedErr) - if err != nil { - require.NotContains(verifier.blkIDToState, blkID) - return - } + require.NoError(blk.Visit(verifier)) + blkID := blk.ID() require.Contains(verifier.blkIDToState, blkID) blockState := verifier.blkIDToState[blkID] require.Equal(blk, blockState.statelessBlock) From 0685531798bf4821c815b86246108a6620795ab6 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 2 Nov 2024 13:43:32 -0400 Subject: [PATCH 276/400] Add tests --- .../block/executor/verifier_test.go | 39 ++---- .../txs/executor/state_changes_test.go | 120 ++++++++++++++++++ 2 files changed, 134 insertions(+), 25 deletions(-) diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 97a1c2ca1e51..043ec8f7caba 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -1224,33 +1224,22 @@ func TestBlockExecutionEvictsLowBalanceSoVs(t *testing.T) { pk = bls.PublicFromSecretKey(sk) pkBytes = bls.PublicKeyToUncompressedBytes(pk) - fractionalTimeSoV0 = state.SubnetOnlyValidator{ - ValidationID: ids.GenerateTestID(), - SubnetID: ids.GenerateTestID(), - NodeID: ids.GenerateTestNodeID(), - PublicKey: pkBytes, - Weight: 1, - EndAccumulatedFee: 5 * units.NanoAvax, // lasts 2.5 seconds - } - fractionalTimeSoV1 = state.SubnetOnlyValidator{ - ValidationID: ids.GenerateTestID(), - SubnetID: ids.GenerateTestID(), - NodeID: ids.GenerateTestNodeID(), - PublicKey: pkBytes, - Weight: 1, - EndAccumulatedFee: 5 * units.NanoAvax, // lasts 2.5 seconds + newSoV = func(endAccumulatedFee uint64) state.SubnetOnlyValidator { + return state.SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: pkBytes, + Weight: 1, + EndAccumulatedFee: endAccumulatedFee, + } } - fractionalSoVEvictedTime = genesistest.DefaultValidatorStartTime.Add(2 * time.Second) // evicts early rather than late + fractionalTimeSoV0 = newSoV(5 * units.NanoAvax) // lasts 2.5 seconds + fractionalTimeSoV1 = newSoV(5 * units.NanoAvax) // lasts 2.5 seconds + wholeTimeSoV = newSoV(8 * units.NanoAvax) // lasts 4 seconds - wholeTimeSoV = state.SubnetOnlyValidator{ - ValidationID: ids.GenerateTestID(), - SubnetID: ids.GenerateTestID(), - NodeID: ids.GenerateTestNodeID(), - PublicKey: pkBytes, - Weight: 1, - EndAccumulatedFee: 8 * units.NanoAvax, // lasts 4 seconds - } - wholeSoVEvictedTime = genesistest.DefaultValidatorStartTime.Add(4 * time.Second) // evicts on time + fractionalSoVEvictedTime = genesistest.DefaultValidatorStartTime.Add(2 * time.Second) // evicts early rather than late + wholeSoVEvictedTime = genesistest.DefaultValidatorStartTime.Add(4 * time.Second) // evicts on time ) tests := []struct { diff --git a/vms/platformvm/txs/executor/state_changes_test.go b/vms/platformvm/txs/executor/state_changes_test.go index 6ae427129201..42477a15e28d 100644 --- a/vms/platformvm/txs/executor/state_changes_test.go +++ b/vms/platformvm/txs/executor/state_changes_test.go @@ -12,8 +12,10 @@ import ( "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/crypto/bls" "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/utils/timer/mockable" + "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/avalanchego/vms/platformvm/config" "github.com/ava-labs/avalanchego/vms/platformvm/genesis/genesistest" @@ -223,3 +225,121 @@ func TestAdvanceTimeTo_RemovesStaleExpiries(t *testing.T) { }) } } + +func TestAdvanceTimeTo_DeactivatesSoVs(t *testing.T) { + sk, err := bls.NewSecretKey() + require.NoError(t, err) + + var ( + pk = bls.PublicFromSecretKey(sk) + pkBytes = bls.PublicKeyToUncompressedBytes(pk) + + newSoV = func(endAccumulatedFee uint64) state.SubnetOnlyValidator { + return state.SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: pkBytes, + Weight: 1, + EndAccumulatedFee: endAccumulatedFee, + } + } + sovToEvict0 = newSoV(3 * units.NanoAvax) // lasts 3 seconds + sovToEvict1 = newSoV(3 * units.NanoAvax) // lasts 3 seconds + sovToKeep = newSoV(units.Avax) + + currentTime = genesistest.DefaultValidatorStartTime + newTime = currentTime.Add(3 * time.Second) + ) + + tests := []struct { + name string + initialSoVs []state.SubnetOnlyValidator + expectedModified bool + expectedSoVs []state.SubnetOnlyValidator + }{ + { + name: "no SoVs", + expectedModified: false, + }, + { + name: "evicted one", + initialSoVs: []state.SubnetOnlyValidator{ + sovToEvict0, + }, + expectedModified: true, + }, + { + name: "evicted multiple", + initialSoVs: []state.SubnetOnlyValidator{ + sovToEvict0, + sovToEvict1, + }, + expectedModified: true, + }, + { + name: "evicted only 2", + initialSoVs: []state.SubnetOnlyValidator{ + sovToEvict0, + sovToEvict1, + sovToKeep, + }, + expectedModified: true, + expectedSoVs: []state.SubnetOnlyValidator{ + sovToKeep, + }, + }, + { + name: "chooses not to evict", + initialSoVs: []state.SubnetOnlyValidator{ + sovToKeep, + }, + expectedModified: false, + expectedSoVs: []state.SubnetOnlyValidator{ + sovToKeep, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var ( + require = require.New(t) + s = statetest.New(t, statetest.Config{}) + ) + + for _, sov := range test.initialSoVs { + require.NoError(s.PutSubnetOnlyValidator(sov)) + } + + // Ensure the invariant that [newTime <= nextStakerChangeTime] on + // AdvanceTimeTo is maintained. + nextStakerChangeTime, err := state.GetNextStakerChangeTime( + genesis.LocalParams.ValidatorFeeConfig, + s, + mockable.MaxTime, + ) + require.NoError(err) + require.False(newTime.After(nextStakerChangeTime)) + + validatorsModified, err := AdvanceTimeTo( + &Backend{ + Config: &config.Config{ + ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, + UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), + }, + }, + s, + newTime, + ) + require.NoError(err) + require.Equal(test.expectedModified, validatorsModified) + + activeSoVs, err := s.GetActiveSubnetOnlyValidatorsIterator() + require.NoError(err) + require.Equal( + test.expectedSoVs, + iterator.ToSlice(activeSoVs), + ) + }) + } +} From bb9f853994404dbb7f58fa651134b0a68df80d9e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 2 Nov 2024 13:50:45 -0400 Subject: [PATCH 277/400] test excess and fees --- .../txs/executor/state_changes_test.go | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/vms/platformvm/txs/executor/state_changes_test.go b/vms/platformvm/txs/executor/state_changes_test.go index 42477a15e28d..944fd7253859 100644 --- a/vms/platformvm/txs/executor/state_changes_test.go +++ b/vms/platformvm/txs/executor/state_changes_test.go @@ -21,6 +21,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/genesis/genesistest" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/state/statetest" + "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) func TestAdvanceTimeTo_UpdatesFeeState(t *testing.T) { @@ -226,10 +227,15 @@ func TestAdvanceTimeTo_RemovesStaleExpiries(t *testing.T) { } } -func TestAdvanceTimeTo_DeactivatesSoVs(t *testing.T) { +func TestAdvanceTimeTo_UpdateSoVs(t *testing.T) { sk, err := bls.NewSecretKey() require.NoError(t, err) + const ( + secondsToAdvance = 3 + timeToAdvance = secondsToAdvance * time.Second + ) + var ( pk = bls.PublicFromSecretKey(sk) pkBytes = bls.PublicKeyToUncompressedBytes(pk) @@ -249,7 +255,7 @@ func TestAdvanceTimeTo_DeactivatesSoVs(t *testing.T) { sovToKeep = newSoV(units.Avax) currentTime = genesistest.DefaultValidatorStartTime - newTime = currentTime.Add(3 * time.Second) + newTime = currentTime.Add(timeToAdvance) ) tests := []struct { @@ -257,10 +263,12 @@ func TestAdvanceTimeTo_DeactivatesSoVs(t *testing.T) { initialSoVs []state.SubnetOnlyValidator expectedModified bool expectedSoVs []state.SubnetOnlyValidator + expectedExcess gas.Gas }{ { name: "no SoVs", expectedModified: false, + expectedExcess: 0, }, { name: "evicted one", @@ -268,6 +276,7 @@ func TestAdvanceTimeTo_DeactivatesSoVs(t *testing.T) { sovToEvict0, }, expectedModified: true, + expectedExcess: 0, }, { name: "evicted multiple", @@ -276,6 +285,7 @@ func TestAdvanceTimeTo_DeactivatesSoVs(t *testing.T) { sovToEvict1, }, expectedModified: true, + expectedExcess: 3, }, { name: "evicted only 2", @@ -288,6 +298,7 @@ func TestAdvanceTimeTo_DeactivatesSoVs(t *testing.T) { expectedSoVs: []state.SubnetOnlyValidator{ sovToKeep, }, + expectedExcess: 6, }, { name: "chooses not to evict", @@ -298,6 +309,7 @@ func TestAdvanceTimeTo_DeactivatesSoVs(t *testing.T) { expectedSoVs: []state.SubnetOnlyValidator{ sovToKeep, }, + expectedExcess: 0, }, } for _, test := range tests { @@ -324,8 +336,14 @@ func TestAdvanceTimeTo_DeactivatesSoVs(t *testing.T) { validatorsModified, err := AdvanceTimeTo( &Backend{ Config: &config.Config{ - ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, - UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), + // ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, + ValidatorFeeConfig: fee.Config{ + Capacity: genesis.LocalParams.ValidatorFeeConfig.Capacity, + Target: 1, + MinPrice: genesis.LocalParams.ValidatorFeeConfig.MinPrice, + ExcessConversionConstant: genesis.LocalParams.ValidatorFeeConfig.ExcessConversionConstant, + }, + UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), }, }, s, @@ -340,6 +358,9 @@ func TestAdvanceTimeTo_DeactivatesSoVs(t *testing.T) { test.expectedSoVs, iterator.ToSlice(activeSoVs), ) + + require.Equal(test.expectedExcess, s.GetSoVExcess()) + require.Equal(uint64(secondsToAdvance), s.GetAccruedFees()) }) } } From 69837c104c0aca3874ed6badc220234eacebceb3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 4 Nov 2024 14:31:34 -0500 Subject: [PATCH 278/400] improve caching --- vms/platformvm/state/state.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index ad09f28457b7..61a697ad31d0 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -850,7 +850,13 @@ func (s *state) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { return weight, nil } - return database.WithDefault(database.GetUInt64, s.weightsDB, subnetID[:], 0) + weight, err := database.WithDefault(database.GetUInt64, s.weightsDB, subnetID[:], 0) + if err != nil { + return 0, err + } + + s.weightsCache.Put(subnetID, weight) + return weight, nil } func (s *state) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { @@ -889,7 +895,13 @@ func (s *state) HasSubnetOnlyValidator(subnetID ids.ID, nodeID ids.NodeID) (bool } key := subnetIDNodeID.Marshal() - return s.subnetIDNodeIDDB.Has(key) + has, err := s.subnetIDNodeIDDB.Has(key) + if err != nil { + return false, err + } + + s.subnetIDNodeIDCache.Put(subnetIDNodeID, has) + return has, nil } func (s *state) PutSubnetOnlyValidator(sov SubnetOnlyValidator) error { From a5d39308c93de8f758ef35fd08c833f5d70290ee Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 4 Nov 2024 15:46:51 -0500 Subject: [PATCH 279/400] cleanup --- .../block/executor/standard_block_test.go | 2 + vms/platformvm/block/executor/verifier.go | 120 ++++++++++-------- .../block/executor/verifier_test.go | 1 + 3 files changed, 69 insertions(+), 54 deletions(-) diff --git a/vms/platformvm/block/executor/standard_block_test.go b/vms/platformvm/block/executor/standard_block_test.go index 303aad8fd282..b45eaf9fbf37 100644 --- a/vms/platformvm/block/executor/standard_block_test.go +++ b/vms/platformvm/block/executor/standard_block_test.go @@ -57,11 +57,13 @@ func TestApricotStandardBlockTimeVerification(t *testing.T) { env.blkManager.(*manager).lastAccepted = parentID chainTime := env.clk.Time().Truncate(time.Second) + onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() onParentAccept.EXPECT().GetAccruedFees().Return(uint64(0)).AnyTimes() onParentAccept.EXPECT().NumActiveSubnetOnlyValidators().Return(0).AnyTimes() + onParentAccept.EXPECT().GetActiveSubnetOnlyValidatorsIterator().Return(&iterator.Empty[state.SubnetOnlyValidator]{}, nil).AnyTimes() // wrong height apricotChildBlk, err := block.NewApricotStandardBlock( diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index 56c030c05366..6f3c4fa22fb2 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -478,9 +478,7 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator txfee.Calcula error, ) { // Complexity is limited first to avoid processing too large of a block. - timestamp := diff.GetTimestamp() - isEtna := v.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) - if isEtna { + if timestamp := diff.GetTimestamp(); v.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) { var blockComplexity gas.Dimensions for _, tx := range txs { txComplexity, err := txfee.TxComplexity(tx.Unsigned) @@ -557,57 +555,6 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator txfee.Calcula } } - // 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 ( - accruedFees = diff.GetAccruedFees() - validatorFeeState = validatorfee.State{ - Current: gas.Gas(diff.NumActiveSubnetOnlyValidators()), - Excess: diff.GetSoVExcess(), - } - potentialCost = validatorFeeState.CostOf( - v.txExecutorBackend.Config.ValidatorFeeConfig, - 1, // 1 second - ) - ) - potentialAccruedFees, err := math.Add(accruedFees, potentialCost) - if err != nil { - return nil, nil, nil, err - } - - // 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 the validator has exactly the right amount of fee for the next - // second we should not remove them here. - 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 } @@ -622,5 +569,70 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator txfee.Calcula } } + // 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. + err := deactivateLowBalanceSoVs( + v.txExecutorBackend.Config.ValidatorFeeConfig, + diff, + ) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to deactivate low balance SoVs: %w", err) + } + return inputs, atomicRequests, onAcceptFunc, nil } + +// deactivateLowBalanceSoVs deactivates any SoVs that might not have sufficient +// fees to pay for the next second. +func deactivateLowBalanceSoVs( + config validatorfee.Config, + diff state.Diff, +) error { + var ( + accruedFees = diff.GetAccruedFees() + validatorFeeState = validatorfee.State{ + Current: gas.Gas(diff.NumActiveSubnetOnlyValidators()), + Excess: diff.GetSoVExcess(), + } + potentialCost = validatorFeeState.CostOf( + config, + 1, // 1 second + ) + ) + potentialAccruedFees, err := math.Add(accruedFees, potentialCost) + if err != nil { + return fmt.Errorf("could not calculate potentially accrued fees: %w", err) + } + + // Invariant: Proposal transactions do not impact SoV state. + sovIterator, err := diff.GetActiveSubnetOnlyValidatorsIterator() + if err != nil { + return fmt.Errorf("could not iterate over active SoVs: %w", err) + } + + var sovsToDeactivate []state.SubnetOnlyValidator + for sovIterator.Next() { + sov := sovIterator.Value() + // 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 + } + + 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 fmt.Errorf("could not deactivate SoV %s: %w", sov.ValidationID, err) + } + } + return nil +} diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 043ec8f7caba..b4920818ce98 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -351,6 +351,7 @@ func TestVerifierVisitStandardBlock(t *testing.T) { parentState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) parentState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) parentState.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) + parentState.EXPECT().GetActiveSubnetOnlyValidatorsIterator().Return(&iterator.Empty[state.SubnetOnlyValidator]{}, nil).Times(1) parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) mempool.EXPECT().Remove(apricotBlk.Txs()).Times(1) From d74cdff1c55dbf62987ebc421d612dc9424fd2e0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 4 Nov 2024 17:12:25 -0500 Subject: [PATCH 280/400] Simplify test --- .../block/executor/verifier_test.go | 82 ++++--------------- 1 file changed, 15 insertions(+), 67 deletions(-) diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index b4920818ce98..ceb848df0561 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -1217,7 +1217,7 @@ func TestBlockExecutionWithComplexity(t *testing.T) { } } -func TestBlockExecutionEvictsLowBalanceSoVs(t *testing.T) { +func TestDeactivateLowBalanceSoVs(t *testing.T) { sk, err := bls.NewSecretKey() require.NoError(t, err) @@ -1235,33 +1235,24 @@ func TestBlockExecutionEvictsLowBalanceSoVs(t *testing.T) { EndAccumulatedFee: endAccumulatedFee, } } - fractionalTimeSoV0 = newSoV(5 * units.NanoAvax) // lasts 2.5 seconds - fractionalTimeSoV1 = newSoV(5 * units.NanoAvax) // lasts 2.5 seconds - wholeTimeSoV = newSoV(8 * units.NanoAvax) // lasts 4 seconds - - fractionalSoVEvictedTime = genesistest.DefaultValidatorStartTime.Add(2 * time.Second) // evicts early rather than late - wholeSoVEvictedTime = genesistest.DefaultValidatorStartTime.Add(4 * time.Second) // evicts on time + fractionalTimeSoV0 = newSoV(1 * units.NanoAvax) // lasts .5 seconds + fractionalTimeSoV1 = newSoV(1 * units.NanoAvax) // lasts .5 seconds + wholeTimeSoV = newSoV(2 * units.NanoAvax) // lasts 1 second ) tests := []struct { name string initialSoVs []state.SubnetOnlyValidator - timestamp time.Time expectedSoVs []state.SubnetOnlyValidator }{ { - name: "no SoVs", - timestamp: genesistest.DefaultValidatorStartTime.Add(10 * time.Second), + name: "no SoVs", }, { - name: "fractional SoVs are not overcharged", + name: "fractional SoV is not undercharged", initialSoVs: []state.SubnetOnlyValidator{ fractionalTimeSoV0, }, - timestamp: fractionalSoVEvictedTime.Add(-time.Second), - expectedSoVs: []state.SubnetOnlyValidator{ - fractionalTimeSoV0, - }, }, { name: "fractional SoVs are not undercharged", @@ -1269,32 +1260,22 @@ func TestBlockExecutionEvictsLowBalanceSoVs(t *testing.T) { fractionalTimeSoV0, fractionalTimeSoV1, }, - timestamp: fractionalSoVEvictedTime, }, { name: "whole SoVs are not overcharged", initialSoVs: []state.SubnetOnlyValidator{ wholeTimeSoV, }, - timestamp: wholeSoVEvictedTime.Add(-time.Second), expectedSoVs: []state.SubnetOnlyValidator{ wholeTimeSoV, }, }, - { - name: "whole SoVs are not undercharged", - initialSoVs: []state.SubnetOnlyValidator{ - wholeTimeSoV, - }, - timestamp: wholeSoVEvictedTime, - }, { name: "partial eviction", initialSoVs: []state.SubnetOnlyValidator{ fractionalTimeSoV0, wholeTimeSoV, }, - timestamp: fractionalSoVEvictedTime, expectedSoVs: []state.SubnetOnlyValidator{ wholeTimeSoV, }, @@ -1309,51 +1290,18 @@ func TestBlockExecutionEvictsLowBalanceSoVs(t *testing.T) { require.NoError(s.PutSubnetOnlyValidator(sov)) } - verifier := newTestVerifier(t, s) - verifier.txExecutorBackend.Clk.Set(test.timestamp) - - wallet := txstest.NewWallet( - t, - verifier.ctx, - verifier.txExecutorBackend.Config, - s, - secp256k1fx.NewKeychain(genesis.EWOQKey), - nil, // subnetIDs - nil, // chainIDs - ) - - baseTx, err := wallet.IssueBaseTx([]*avax.TransferableOutput{}) + diff, err := state.NewDiffOn(s) require.NoError(err) - timestamp, _, err := state.NextBlockTime( - verifier.txExecutorBackend.Config.ValidatorFeeConfig, - s, - verifier.txExecutorBackend.Clk, - ) - require.NoError(err) - - lastAcceptedID := s.GetLastAccepted() - lastAccepted, err := s.GetStatelessBlock(lastAcceptedID) - require.NoError(err) - - blk, err := block.NewBanffStandardBlock( - timestamp, - lastAcceptedID, - lastAccepted.Height()+1, - []*txs.Tx{ - baseTx, - }, - ) - require.NoError(err) - - require.NoError(blk.Visit(verifier)) - - blkID := blk.ID() - require.Contains(verifier.blkIDToState, blkID) - blockState := verifier.blkIDToState[blkID] - require.Equal(blk, blockState.statelessBlock) + config := validatorfee.Config{ + Capacity: genesis.LocalParams.ValidatorFeeConfig.Capacity, + Target: genesis.LocalParams.ValidatorFeeConfig.Target, + MinPrice: gas.Price(2 * units.NanoAvax), // Min price is increased to allow fractional fees + ExcessConversionConstant: genesis.LocalParams.ValidatorFeeConfig.ExcessConversionConstant, + } + require.NoError(deactivateLowBalanceSoVs(config, diff)) - sovs, err := blockState.onAcceptState.GetActiveSubnetOnlyValidatorsIterator() + sovs, err := diff.GetActiveSubnetOnlyValidatorsIterator() require.NoError(err) require.Equal( test.expectedSoVs, From 7cddfc0d5147ed8d0a120ac67809f1a6c09b55e8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 4 Nov 2024 17:13:11 -0500 Subject: [PATCH 281/400] reduce diff --- vms/platformvm/block/executor/standard_block_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/vms/platformvm/block/executor/standard_block_test.go b/vms/platformvm/block/executor/standard_block_test.go index b45eaf9fbf37..055e6c0e813a 100644 --- a/vms/platformvm/block/executor/standard_block_test.go +++ b/vms/platformvm/block/executor/standard_block_test.go @@ -57,7 +57,6 @@ func TestApricotStandardBlockTimeVerification(t *testing.T) { env.blkManager.(*manager).lastAccepted = parentID chainTime := env.clk.Time().Truncate(time.Second) - onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes() onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes() onParentAccept.EXPECT().GetSoVExcess().Return(gas.Gas(0)).AnyTimes() From afc905487744dd89d7e78b172d771d98f4450cc8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 4 Nov 2024 17:15:01 -0500 Subject: [PATCH 282/400] nit --- vms/platformvm/block/executor/verifier_test.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index ceb848df0561..d0b88c197de6 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -76,15 +76,10 @@ func newTestVerifier(t testing.TB, s state.State) *verifier { }, txExecutorBackend: &executor.Backend{ Config: &config.Config{ - CreateAssetTxFee: genesis.LocalParams.CreateAssetTxFee, - StaticFeeConfig: genesis.LocalParams.StaticFeeConfig, - DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, - ValidatorFeeConfig: validatorfee.Config{ - Capacity: genesis.LocalParams.ValidatorFeeConfig.Capacity, - Target: genesis.LocalParams.ValidatorFeeConfig.Target, - MinPrice: gas.Price(2 * units.NanoAvax), // Min price is increased to allow fractional fees - ExcessConversionConstant: genesis.LocalParams.ValidatorFeeConfig.ExcessConversionConstant, - }, + CreateAssetTxFee: genesis.LocalParams.CreateAssetTxFee, + StaticFeeConfig: genesis.LocalParams.StaticFeeConfig, + DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, + ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, SybilProtectionEnabled: true, UpgradeConfig: upgrades, }, From 3c8246ee5e2c0fb78893ac1c65dda21b2713ab43 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 4 Nov 2024 17:16:34 -0500 Subject: [PATCH 283/400] nit --- vms/platformvm/state/chain_time_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/state/chain_time_helpers.go b/vms/platformvm/state/chain_time_helpers.go index 17d466bcea40..f19e0491842b 100644 --- a/vms/platformvm/state/chain_time_helpers.go +++ b/vms/platformvm/state/chain_time_helpers.go @@ -49,7 +49,7 @@ func NextBlockTime( } // GetNextStakerChangeTime returns the next time a staker will be either added -// or removed to/from the validator set. If the next staker change time is +// to or removed from the validator set. If the next staker change time is // further in the future than [nextTime], then [nextTime] is returned. func GetNextStakerChangeTime( config validatorfee.Config, From 1268ed1419c9c7eefead0c5b9d878cf27a665a0f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 4 Nov 2024 17:25:26 -0500 Subject: [PATCH 284/400] nit --- vms/platformvm/state/chain_time_helpers.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/state/chain_time_helpers.go b/vms/platformvm/state/chain_time_helpers.go index f19e0491842b..d31cacafb1a2 100644 --- a/vms/platformvm/state/chain_time_helpers.go +++ b/vms/platformvm/state/chain_time_helpers.go @@ -80,9 +80,17 @@ func GetNextStakerChangeTime( } } + return getNextSoVEvictionTime(config, state, nextTime) +} + +func getNextSoVEvictionTime( + config validatorfee.Config, + state Chain, + nextTime time.Time, +) (time.Time, error) { sovIterator, err := state.GetActiveSubnetOnlyValidatorsIterator() if err != nil { - return time.Time{}, err + return time.Time{}, fmt.Errorf("could not iterate over active SoVs: %w", err) } defer sovIterator.Release() @@ -98,7 +106,7 @@ func GetNextStakerChangeTime( ) remainingFunds, err := math.Sub(sov.EndAccumulatedFee, accruedFees) if err != nil { - return time.Time{}, err + return time.Time{}, fmt.Errorf("could not calculate remaining funds: %w", err) } // Calculate how many seconds the remaining funds can last for. From f7b75bc9e8efca30d86ee2932a604634fdff901e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 4 Nov 2024 18:01:03 -0500 Subject: [PATCH 285/400] nits --- vms/platformvm/txs/executor/state_changes.go | 62 +++++++++++++------ .../txs/executor/state_changes_test.go | 21 ++++--- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/vms/platformvm/txs/executor/state_changes.go b/vms/platformvm/txs/executor/state_changes.go index d01675637b28..6bb1b5bce2f3 100644 --- a/vms/platformvm/txs/executor/state_changes.go +++ b/vms/platformvm/txs/executor/state_changes.go @@ -260,28 +260,56 @@ func advanceTimeTo( previousChainTime := changes.GetTimestamp() duration := uint64(newChainTime.Sub(previousChainTime) / time.Second) + advanceDynamicFeeState(backend.Config.DynamicFeeConfig, changes, duration) + sovsChanged, err := advanceValidatorFeeState( + backend.Config.ValidatorFeeConfig, + parentState, + changes, + duration, + ) + if err != nil { + return nil, false, fmt.Errorf("failed to advance validator fee state: %w", err) + } + changed = changed || sovsChanged + + changes.SetTimestamp(newChainTime) + return changes, changed, nil +} + +func advanceDynamicFeeState( + config gas.Config, + changes state.Diff, + seconds uint64, +) { dynamicFeeState := changes.GetFeeState() dynamicFeeState = dynamicFeeState.AdvanceTime( - backend.Config.DynamicFeeConfig.MaxCapacity, - backend.Config.DynamicFeeConfig.MaxPerSecond, - backend.Config.DynamicFeeConfig.TargetPerSecond, - duration, + config.MaxCapacity, + config.MaxPerSecond, + config.TargetPerSecond, + seconds, ) changes.SetFeeState(dynamicFeeState) +} +// advanceValidatorFeeState advances the validator fee state by [seconds]. SoVs +// are read from [parentState] and written to [changes] to avoid modifying state +// while an iterator is held. +func advanceValidatorFeeState( + config fee.Config, + parentState state.Chain, + changes state.Diff, + seconds uint64, +) (bool, error) { validatorFeeState := fee.State{ Current: gas.Gas(changes.NumActiveSubnetOnlyValidators()), Excess: changes.GetSoVExcess(), } - validatorCost := validatorFeeState.CostOf( - backend.Config.ValidatorFeeConfig, - duration, - ) + validatorCost := validatorFeeState.CostOf(config, seconds) accruedFees := changes.GetAccruedFees() - accruedFees, err = math.Add(accruedFees, validatorCost) + accruedFees, err := math.Add(accruedFees, validatorCost) if err != nil { - return nil, false, err + return false, fmt.Errorf("could not calculate accrued fees: %w", err) } // Invariant: It is not safe to modify the state while iterating over it, @@ -289,10 +317,11 @@ func advanceTimeTo( // ParentState must not be modified before this iterator is released. sovIterator, err := parentState.GetActiveSubnetOnlyValidatorsIterator() if err != nil { - return nil, false, err + return false, fmt.Errorf("could not iterate over active SoVs: %w", err) } defer sovIterator.Release() + var changed bool for sovIterator.Next() { sov := sovIterator.Value() if sov.EndAccumulatedFee > accruedFees { @@ -301,19 +330,16 @@ func advanceTimeTo( sov.EndAccumulatedFee = 0 // Deactivate the validator if err := changes.PutSubnetOnlyValidator(sov); err != nil { - return nil, false, err + return false, fmt.Errorf("could not deactivate SoV %s: %w", sov.ValidationID, err) } changed = true } - validatorFeeState = validatorFeeState.AdvanceTime( - backend.Config.ValidatorFeeConfig.Target, - duration, - ) + // + validatorFeeState = validatorFeeState.AdvanceTime(config.Target, seconds) changes.SetSoVExcess(validatorFeeState.Excess) changes.SetAccruedFees(accruedFees) - changes.SetTimestamp(newChainTime) - return changes, changed, nil + return changed, nil } func GetRewardsCalculator( diff --git a/vms/platformvm/txs/executor/state_changes_test.go b/vms/platformvm/txs/executor/state_changes_test.go index 944fd7253859..26088e1c8a59 100644 --- a/vms/platformvm/txs/executor/state_changes_test.go +++ b/vms/platformvm/txs/executor/state_changes_test.go @@ -256,6 +256,16 @@ func TestAdvanceTimeTo_UpdateSoVs(t *testing.T) { currentTime = genesistest.DefaultValidatorStartTime newTime = currentTime.Add(timeToAdvance) + + config = config.Config{ + ValidatorFeeConfig: fee.Config{ + Capacity: genesis.LocalParams.ValidatorFeeConfig.Capacity, + Target: 1, + MinPrice: genesis.LocalParams.ValidatorFeeConfig.MinPrice, + ExcessConversionConstant: genesis.LocalParams.ValidatorFeeConfig.ExcessConversionConstant, + }, + UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), + } ) tests := []struct { @@ -335,16 +345,7 @@ func TestAdvanceTimeTo_UpdateSoVs(t *testing.T) { validatorsModified, err := AdvanceTimeTo( &Backend{ - Config: &config.Config{ - // ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, - ValidatorFeeConfig: fee.Config{ - Capacity: genesis.LocalParams.ValidatorFeeConfig.Capacity, - Target: 1, - MinPrice: genesis.LocalParams.ValidatorFeeConfig.MinPrice, - ExcessConversionConstant: genesis.LocalParams.ValidatorFeeConfig.ExcessConversionConstant, - }, - UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), - }, + Config: &config, }, s, newTime, From 82a249ba6557fbab08369e1debd0481e3215989c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 4 Nov 2024 18:01:54 -0500 Subject: [PATCH 286/400] nit --- vms/platformvm/txs/executor/state_changes.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vms/platformvm/txs/executor/state_changes.go b/vms/platformvm/txs/executor/state_changes.go index 6bb1b5bce2f3..392d3fc8b5a4 100644 --- a/vms/platformvm/txs/executor/state_changes.go +++ b/vms/platformvm/txs/executor/state_changes.go @@ -256,16 +256,16 @@ func advanceTimeTo( return changes, changed, nil } - // Advance the dynamic fees state + // Calculate number of seconds the time is advancing previousChainTime := changes.GetTimestamp() - duration := uint64(newChainTime.Sub(previousChainTime) / time.Second) + seconds := uint64(newChainTime.Sub(previousChainTime) / time.Second) - advanceDynamicFeeState(backend.Config.DynamicFeeConfig, changes, duration) + advanceDynamicFeeState(backend.Config.DynamicFeeConfig, changes, seconds) sovsChanged, err := advanceValidatorFeeState( backend.Config.ValidatorFeeConfig, parentState, changes, - duration, + seconds, ) if err != nil { return nil, false, fmt.Errorf("failed to advance validator fee state: %w", err) From d37e9f3b116146cf52d4e3f76944e268bdfb52d8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 4 Nov 2024 19:00:33 -0500 Subject: [PATCH 287/400] nit --- 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 61a697ad31d0..67be6717fa44 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1670,7 +1670,6 @@ func (s *state) loadExpiry() error { func (s *state) loadActiveSubnetOnlyValidators() error { it := s.activeDB.NewIterator() defer it.Release() - for it.Next() { key := it.Key() validationID, err := ids.ToID(key) From 9dc642a4c832f9a0305f60fcb37b8cf288253c54 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 4 Nov 2024 19:07:06 -0500 Subject: [PATCH 288/400] num -> net for possibly negative value --- vms/platformvm/state/diff.go | 2 +- vms/platformvm/state/state.go | 2 +- vms/platformvm/state/subnet_only_validator.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 4b8922f74492..30ee112a41b9 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -213,7 +213,7 @@ func (d *diff) GetActiveSubnetOnlyValidatorsIterator() (iterator.Iterator[Subnet } func (d *diff) NumActiveSubnetOnlyValidators() int { - return d.parentActiveSOVs + d.sovDiff.numAddedActive + return d.parentActiveSOVs + d.sovDiff.netAddedActive } func (d *diff) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 67be6717fa44..3ec8527a258b 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -838,7 +838,7 @@ func (s *state) GetActiveSubnetOnlyValidatorsIterator() (iterator.Iterator[Subne } func (s *state) NumActiveSubnetOnlyValidators() int { - return s.activeSOVs.len() + s.sovDiff.numAddedActive + return s.activeSOVs.len() + s.sovDiff.netAddedActive } func (s *state) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { diff --git a/vms/platformvm/state/subnet_only_validator.go b/vms/platformvm/state/subnet_only_validator.go index bb9af0d78ca2..109fd23ea9f2 100644 --- a/vms/platformvm/state/subnet_only_validator.go +++ b/vms/platformvm/state/subnet_only_validator.go @@ -250,7 +250,7 @@ func deleteSubnetOnlyValidator( } type subnetOnlyValidatorsDiff struct { - numAddedActive int // May be negative + netAddedActive int // May be negative modifiedTotalWeight map[ids.ID]uint64 // subnetID -> totalWeight modified map[ids.ID]SubnetOnlyValidator modifiedHasNodeIDs map[subnetIDNodeID]bool @@ -344,9 +344,9 @@ func (d *subnetOnlyValidatorsDiff) putSubnetOnlyValidator(state Chain, sov Subne switch { case prevActive && !newActive: - d.numAddedActive-- + d.netAddedActive-- case !prevActive && newActive: - d.numAddedActive++ + d.netAddedActive++ } if prevSOV, ok := d.modified[sov.ValidationID]; ok { From 22de2b13b1e8bcb9024074250d98f6c8eb0e9c0d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 10:52:20 -0500 Subject: [PATCH 289/400] Address PR comments --- vms/platformvm/state/diff.go | 34 +++++++++++++++--------------- vms/platformvm/state/state.go | 10 +++++---- vms/platformvm/state/state_test.go | 24 +++++++++++++++++++++ 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index 30ee112a41b9..317e4e210142 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -35,11 +35,11 @@ type diff struct { parentID ids.ID stateVersions Versions - timestamp time.Time - feeState gas.State - sovExcess gas.Gas - accruedFees uint64 - parentActiveSOVs int + timestamp time.Time + feeState gas.State + sovExcess gas.Gas + accruedFees uint64 + parentNumActiveSOVs int // Subnet ID --> supply of native asset of the subnet currentSupply map[ids.ID]uint64 @@ -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), - subnetConversions: make(map[ids.ID]SubnetConversion), + parentID: parentID, + stateVersions: stateVersions, + timestamp: parentState.GetTimestamp(), + feeState: parentState.GetFeeState(), + sovExcess: parentState.GetSoVExcess(), + accruedFees: parentState.GetAccruedFees(), + parentNumActiveSOVs: parentState.NumActiveSubnetOnlyValidators(), + expiryDiff: newExpiryDiff(), + sovDiff: newSubnetOnlyValidatorsDiff(), + subnetOwners: make(map[ids.ID]fx.Owner), + subnetConversions: make(map[ids.ID]SubnetConversion), }, nil } @@ -213,7 +213,7 @@ func (d *diff) GetActiveSubnetOnlyValidatorsIterator() (iterator.Iterator[Subnet } func (d *diff) NumActiveSubnetOnlyValidators() int { - return d.parentActiveSOVs + d.sovDiff.netAddedActive + return d.parentNumActiveSOVs + d.sovDiff.netAddedActive } func (d *diff) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 3ec8527a258b..12eebd953132 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -872,7 +872,7 @@ func (s *state) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator // getPersistedSubnetOnlyValidator returns the currently persisted // SubnetOnlyValidator with the given validationID. It is guaranteed that any -// returned validator is either active or inactive. +// returned validator is either active or inactive (not deleted). func (s *state) getPersistedSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { if sov, ok := s.activeSOVs.get(validationID); ok { return sov, nil @@ -2506,7 +2506,7 @@ func (s *state) calculateValidatorDiffs() (map[subnetIDNodeID]*validatorDiff, er subnetID: priorSOV.SubnetID, nodeID: priorSOV.effectiveNodeID(), } - diff := getOrDefault(changes, subnetIDNodeID) + diff := getOrSetDefault(changes, subnetIDNodeID) if err := diff.weightDiff.Sub(priorSOV.Weight); err != nil { return nil, err } @@ -2526,7 +2526,7 @@ func (s *state) calculateValidatorDiffs() (map[subnetIDNodeID]*validatorDiff, er subnetID: sov.SubnetID, nodeID: sov.effectiveNodeID(), } - diff := getOrDefault(changes, subnetIDNodeID) + diff := getOrSetDefault(changes, subnetIDNodeID) if err := diff.weightDiff.Add(sov.Weight); err != nil { return nil, err } @@ -2571,7 +2571,9 @@ func (s *state) writeValidatorDiffs(height uint64) error { return nil } -func getOrDefault[K comparable, V any](m map[K]*V, k K) *V { +// getOrSetDefault returns the value at k in m if it exists. If it doesn't +// exist, it sets m[k] to a new value and returns that value. +func getOrSetDefault[K comparable, V any](m map[K]*V, k K) *V { if v, ok := m[k]; ok { return v } diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 0a27b92d7dde..342365a6db46 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1774,6 +1774,27 @@ func TestSubnetOnlyValidators(t *testing.T) { }, }, }, + { + name: "add multiple inactive", + sovs: []SubnetOnlyValidator{ + { + ValidationID: ids.GenerateTestID(), + SubnetID: sov.SubnetID, + NodeID: ids.GenerateTestNodeID(), + PublicKey: pkBytes, + 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 + }, + }, + }, } for _, test := range tests { @@ -1788,6 +1809,9 @@ func TestSubnetOnlyValidators(t *testing.T) { subnetIDs set.Set[ids.ID] ) for _, sov := range test.initial { + // The codec creates zero length slices rather than leaving them + // as nil, so we need to populate the slices for later reflect + // based equality checks. sov.RemainingBalanceOwner = []byte{} sov.DeactivationOwner = []byte{} From cc0e0ee1ae8d3ba8cdac33eef062acfbfe4f0220 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 11:21:40 -0500 Subject: [PATCH 290/400] add comments --- vms/platformvm/block/executor/verifier.go | 3 + vms/platformvm/state/chain_time_helpers.go | 2 + vms/platformvm/txs/executor/state_changes.go | 71 +++++++++++--------- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index 6f3c4fa22fb2..1570a1b3c2ac 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -617,6 +617,9 @@ func deactivateLowBalanceSoVs( sov := sovIterator.Value() // If the validator has exactly the right amount of fee for the next // second we should not remove them here. + // + // GetActiveSubnetOnlyValidatorsIterator iterates in order of increasing + // EndAccumulatedFee, so we can break early. if sov.EndAccumulatedFee >= potentialAccruedFees { break } diff --git a/vms/platformvm/state/chain_time_helpers.go b/vms/platformvm/state/chain_time_helpers.go index d31cacafb1a2..fbf33b83e8d3 100644 --- a/vms/platformvm/state/chain_time_helpers.go +++ b/vms/platformvm/state/chain_time_helpers.go @@ -101,6 +101,8 @@ func getNextSoVEvictionTime( // Calculate the remaining funds that the next validator to evict has. var ( + // GetActiveSubnetOnlyValidatorsIterator iterates in order of increasing + // EndAccumulatedFee, so the first SoV is the next SoV to evict. sov = sovIterator.Value() accruedFees = state.GetAccruedFees() ) diff --git a/vms/platformvm/txs/executor/state_changes.go b/vms/platformvm/txs/executor/state_changes.go index 392d3fc8b5a4..da1ea5b9baaa 100644 --- a/vms/platformvm/txs/executor/state_changes.go +++ b/vms/platformvm/txs/executor/state_changes.go @@ -221,41 +221,16 @@ func advanceTimeTo( changed = true } - // Remove all expiries whose timestamp now implies they can never be - // re-issued. - // - // The expiry timestamp is the time at which it is no longer valid, so any - // expiry with a timestamp less than or equal to the new chain time can be - // removed. - // - // Ref: https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/77-reinventing-subnets#registersubnetvalidatortx - // - // The expiry iterator is sorted in order of increasing timestamp. - // - // Invariant: It is not safe to modify the state while iterating over it, - // so we use the parentState's iterator rather than the changes iterator. - // ParentState must not be modified before this iterator is released. - expiryIterator, err := parentState.GetExpiryIterator() - if err != nil { - return nil, false, err - } - defer expiryIterator.Release() - - newChainTimeUnix := uint64(newChainTime.Unix()) - for expiryIterator.Next() { - expiry := expiryIterator.Value() - if expiry.Timestamp > newChainTimeUnix { - break - } - - changes.DeleteExpiry(expiry) - } - if !backend.Config.UpgradeConfig.IsEtnaActivated(newChainTime) { changes.SetTimestamp(newChainTime) return changes, changed, nil } + newChainTimeUnix := uint64(newChainTime.Unix()) + if err := removeStaleExpiries(parentState, changes, newChainTimeUnix); err != nil { + return nil, false, fmt.Errorf("failed to remove stale expiries: %w", err) + } + // Calculate number of seconds the time is advancing previousChainTime := changes.GetTimestamp() seconds := uint64(newChainTime.Sub(previousChainTime) / time.Second) @@ -276,6 +251,40 @@ func advanceTimeTo( return changes, changed, nil } +// Remove all expiries whose timestamp now implies they can never be re-issued. +// +// The expiry timestamp is the time at which it is no longer valid, so any +// expiry with a timestamp less than or equal to the new chain time can be +// removed. +// +// Ref: https://github.com/avalanche-foundation/ACPs/tree/e333b335c34c8692d84259d21bd07b2bb849dc2c/ACPs/77-reinventing-subnets#registerl1validatortx +// +// The expiry iterator is sorted in order of increasing timestamp. +func removeStaleExpiries( + parentState state.Chain, + changes state.Diff, + newChainTimeUnix uint64, +) error { + // Invariant: It is not safe to modify the state while iterating over it, so + // we use the parentState's iterator rather than the changes iterator. + // ParentState must not be modified before this iterator is released. + expiryIterator, err := parentState.GetExpiryIterator() + if err != nil { + return fmt.Errorf("could not iterate over expiries: %w", err) + } + defer expiryIterator.Release() + + for expiryIterator.Next() { + expiry := expiryIterator.Value() + if expiry.Timestamp > newChainTimeUnix { + break + } + + changes.DeleteExpiry(expiry) + } + return nil +} + func advanceDynamicFeeState( config gas.Config, changes state.Diff, @@ -324,6 +333,8 @@ func advanceValidatorFeeState( var changed bool for sovIterator.Next() { sov := sovIterator.Value() + // GetActiveSubnetOnlyValidatorsIterator iterates in order of increasing + // EndAccumulatedFee, so we can break early. if sov.EndAccumulatedFee > accruedFees { break } From 915eb715bb54db64eddc09d08500c166af2abc5c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 11:28:40 -0500 Subject: [PATCH 291/400] nit --- vms/platformvm/txs/executor/state_changes.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/txs/executor/state_changes.go b/vms/platformvm/txs/executor/state_changes.go index da1ea5b9baaa..0992fd178fcb 100644 --- a/vms/platformvm/txs/executor/state_changes.go +++ b/vms/platformvm/txs/executor/state_changes.go @@ -258,8 +258,6 @@ func advanceTimeTo( // removed. // // Ref: https://github.com/avalanche-foundation/ACPs/tree/e333b335c34c8692d84259d21bd07b2bb849dc2c/ACPs/77-reinventing-subnets#registerl1validatortx -// -// The expiry iterator is sorted in order of increasing timestamp. func removeStaleExpiries( parentState state.Chain, changes state.Diff, @@ -276,6 +274,8 @@ func removeStaleExpiries( for expiryIterator.Next() { expiry := expiryIterator.Value() + // The expiry iterator is sorted in order of increasing timestamp. Once + // we find a non-expired expiry, we can break. if expiry.Timestamp > newChainTimeUnix { break } From 74edd4f3614725d18208a345fb8f6d4db8246650 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 14:55:27 -0500 Subject: [PATCH 292/400] reduce diff --- wallet/subnet/primary/examples/create-chain/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/subnet/primary/examples/create-chain/main.go b/wallet/subnet/primary/examples/create-chain/main.go index 61526d9cf38a..9e32490d9aef 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 := "29uVeLPJB1eQJkzRemU8g8wZDw5uJRqpab5U2mX9euieVwiEbL" genesis := &xsgenesis.Genesis{ Timestamp: time.Now().Unix(), Allocations: []xsgenesis.Allocation{ From 7d31e7a155055cdd2263cb7c699dbce8cbec3476 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 14:59:22 -0500 Subject: [PATCH 293/400] nit --- wallet/chain/p/builder/builder.go | 2 +- wallet/chain/p/wallet/wallet.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index d4c2b75e827d..ef23585ac92e 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -155,7 +155,7 @@ 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 + // - [validators] specifies the initial SoVs of the L1 NewConvertSubnetTx( subnetID ids.ID, chainID ids.ID, diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index 1e0c520b5eca..a36522cc6b3a 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -141,7 +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 + // - [validators] specifies the initial SoVs of the L1 IssueConvertSubnetTx( subnetID ids.ID, chainID ids.ID, From fca9460ac18705900bb8dbcc7593c0909692a86d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 15:00:36 -0500 Subject: [PATCH 294/400] nit --- wallet/chain/p/builder_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/chain/p/builder_test.go b/wallet/chain/p/builder_test.go index ed71768b0969..f56a0eb0896b 100644 --- a/wallet/chain/p/builder_test.go +++ b/wallet/chain/p/builder_test.go @@ -740,7 +740,7 @@ func TestConvertSubnetTx(t *testing.T) { nil, nil, map[ids.ID]uint64{ - e.context.AVAXAssetID: 3 * units.Avax, + e.context.AVAXAssetID: 3 * units.Avax, // Balance of the validators }, ) }) From 158621cb65c750243abeb07ac90ef271e233bce9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 15:03:26 -0500 Subject: [PATCH 295/400] nit --- wallet/chain/p/builder/builder.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index ef23585ac92e..e58e0931b290 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -788,19 +788,23 @@ func (b *builder) NewConvertSubnetTx( options ...common.Option, ) (*txs.ConvertSubnetTx, error) { var ( - toBurn = map[ids.ID]uint64{} - err error - avaxAssetID = b.context.AVAXAssetID + avaxToBurn uint64 + err error ) for _, vdr := range validators { - toBurn[avaxAssetID], err = math.Add(toBurn[avaxAssetID], vdr.Balance) + avaxToBurn, err = math.Add(avaxToBurn, vdr.Balance) if err != nil { return nil, err } } - toStake := map[ids.ID]uint64{} - ops := common.NewOptions(options) + var ( + toBurn = map[ids.ID]uint64{ + b.context.AVAXAssetID: avaxToBurn, + } + toStake = map[ids.ID]uint64{} + ops = common.NewOptions(options) + ) subnetAuth, err := b.authorizeSubnet(subnetID, ops) if err != nil { return nil, err From 4c6462cd7d42692988c44df3f3c590cd98ba29be Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 15:04:30 -0500 Subject: [PATCH 296/400] nit --- 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 b7133488f6ef..8602c26ffb64 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("E8nTR9TtRwfkS7XFjTYUYHENQ91mkPMtDUwwCeu7rNgBBtkqu") From a217d1b160d20fdfeb6751288696d0e61474cee8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 15:05:55 -0500 Subject: [PATCH 297/400] nit --- wallet/chain/p/builder/builder.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index e58e0931b290..e8762d7b2a68 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -787,11 +787,9 @@ func (b *builder) NewConvertSubnetTx( validators []*txs.ConvertSubnetValidator, options ...common.Option, ) (*txs.ConvertSubnetTx, error) { - var ( - avaxToBurn uint64 - err error - ) + var avaxToBurn uint64 for _, vdr := range validators { + var err error avaxToBurn, err = math.Add(avaxToBurn, vdr.Balance) if err != nil { return nil, err From 3987922fbcdd98185564a63c49e6bb7c3de08fd6 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 15:28:10 -0500 Subject: [PATCH 298/400] nit --- .../txs/executor/standard_tx_executor.go | 2 - .../txs/executor/standard_tx_executor_test.go | 51 +++++++++++++++---- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 8c549b677923..d30049cc92c3 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -539,8 +539,6 @@ 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 diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index d32c4e84ea95..62b3120d046e 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -2602,23 +2602,24 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { []ids.ID{subnetID}, nil, // chainIDs ) - chainID = ids.GenerateTestID() - address = utils.RandomBytes(32) - pop = signer.NewProofOfPossession(sk) + chainID = ids.GenerateTestID() + address = utils.RandomBytes(32) + pop = signer.NewProofOfPossession(sk) + validator = &txs.ConvertSubnetValidator{ + NodeID: nodeID.Bytes(), + Weight: weight, + Balance: balance, + Signer: *pop, + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{}, + } ) convertSubnetTx, err := wallet.IssueConvertSubnetTx( subnetID, chainID, address, []*txs.ConvertSubnetValidator{ - { - NodeID: nodeID.Bytes(), - Weight: weight, - Balance: balance, - Signer: *pop, - RemainingBalanceOwner: message.PChainOwner{}, - DeactivationOwner: message.PChainOwner{}, - }, + validator, }, test.builderOptions..., ) @@ -2685,6 +2686,34 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { }, stateConversion, ) + + var ( + validationID = subnetID.Append(0) + pkBytes = bls.PublicKeyToUncompressedBytes(bls.PublicFromSecretKey(sk)) + ) + remainingBalanceOwner, err := txs.Codec.Marshal(txs.CodecVersion, &validator.RemainingBalanceOwner) + require.NoError(err) + + deactivationOwner, err := txs.Codec.Marshal(txs.CodecVersion, &validator.DeactivationOwner) + require.NoError(err) + + sov, err := diff.GetSubnetOnlyValidator(validationID) + require.NoError(err) + require.Equal( + state.SubnetOnlyValidator{ + ValidationID: validationID, + SubnetID: subnetID, + NodeID: nodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: remainingBalanceOwner, + DeactivationOwner: deactivationOwner, + StartTime: uint64(diff.GetTimestamp().Unix()), + Weight: validator.Weight, + MinNonce: 0, + EndAccumulatedFee: validator.Balance + diff.GetAccruedFees(), + }, + sov, + ) }) } } From 56475cf2078bc9e82492e760780fd2769e189995 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 15:51:25 -0500 Subject: [PATCH 299/400] Add nodeID tests --- vms/platformvm/txs/convert_subnet_tx_test.go | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/vms/platformvm/txs/convert_subnet_tx_test.go b/vms/platformvm/txs/convert_subnet_tx_test.go index c627c9711632..d2e91acae0af 100644 --- a/vms/platformvm/txs/convert_subnet_tx_test.go +++ b/vms/platformvm/txs/convert_subnet_tx_test.go @@ -18,6 +18,7 @@ import ( "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/hashing" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/signer" @@ -677,6 +678,42 @@ func TestConvertSubnetTxSyntacticVerify(t *testing.T) { }, expectedErr: ErrZeroWeight, }, + { + name: "invalid validator nodeID length", + tx: &ConvertSubnetTx{ + BaseTx: validBaseTx, + Subnet: validSubnetID, + Validators: []*ConvertSubnetValidator{ + { + NodeID: utils.RandomBytes(ids.NodeIDLen + 1), + Weight: 1, + Signer: *signer.NewProofOfPossession(sk), + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{}, + }, + }, + SubnetAuth: validSubnetAuth, + }, + expectedErr: hashing.ErrInvalidHashLen, + }, + { + name: "invalid validator nodeID", + tx: &ConvertSubnetTx{ + BaseTx: validBaseTx, + Subnet: validSubnetID, + Validators: []*ConvertSubnetValidator{ + { + NodeID: ids.EmptyNodeID[:], + Weight: 1, + Signer: *signer.NewProofOfPossession(sk), + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{}, + }, + }, + SubnetAuth: validSubnetAuth, + }, + expectedErr: errEmptyNodeID, + }, { name: "invalid validator pop", tx: &ConvertSubnetTx{ From db85cf1fc5a77baaadc1712ea7aaa0d96a3a25fc Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 16:01:04 -0500 Subject: [PATCH 300/400] ACP-77: Refactor e2e test --- tests/e2e/p/l1.go | 147 ++++++++++++++++++++++++ tests/e2e/p/permissionless_layer_one.go | 102 ---------------- 2 files changed, 147 insertions(+), 102 deletions(-) create mode 100644 tests/e2e/p/l1.go delete mode 100644 tests/e2e/p/permissionless_layer_one.go diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go new file mode 100644 index 000000000000..7466f82323b8 --- /dev/null +++ b/tests/e2e/p/l1.go @@ -0,0 +1,147 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +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/ids" + "github.com/ava-labs/avalanchego/tests/fixture/e2e" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/vms/example/xsvm/genesis" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + + platformvmsdk "github.com/ava-labs/avalanchego/vms/platformvm" +) + +var _ = e2e.DescribePChain("[L1]", func() { + tc := e2e.NewTestContext() + require := require.New(tc) + + ginkgo.It("creates and updates L1 validators", func() { + env := e2e.GetEnv(tc) + nodeURI := env.GetRandomNodeURI() + + tc.By("verifying Etna is activated", func() { + infoClient := info.NewClient(nodeURI.URI) + upgrades, err := infoClient.Upgrades(tc.DefaultContext()) + require.NoError(err) + + now := time.Now() + if !upgrades.IsEtnaActivated(now) { + ginkgo.Skip("Etna is not activated. L1s are enabled post-Etna, skipping test.") + } + }) + + 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) + + 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) + + var subnetID ids.ID + tc.By("issuing a CreateSubnetTx", func() { + subnetTx, err := pWallet.IssueCreateSubnetTx( + owner, + tc.WithDefaultContext(), + ) + require.NoError(err) + + subnetID = subnetTx.ID() + }) + + 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, + }, + subnet, + ) + }) + + 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() + }) + + address := []byte{} + tc.By("issuing a ConvertSubnetTx", func() { + _, err := pWallet.IssueConvertSubnetTx( + subnetID, + chainID, + address, + tc.WithDefaultContext(), + ) + require.NoError(err) + }) + + tc.By("verifying the Permissioned Subnet was converted to an L1", func() { + 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, + ManagerChainID: chainID, + ManagerAddress: address, + }, + subnet, + ) + }) + }) + + _ = e2e.CheckBootstrapIsPossible(tc, env.GetNetwork()) + }) +}) diff --git a/tests/e2e/p/permissionless_layer_one.go b/tests/e2e/p/permissionless_layer_one.go deleted file mode 100644 index edd1dbf7f9e4..000000000000 --- a/tests/e2e/p/permissionless_layer_one.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package p - -import ( - "time" - - "github.com/onsi/ginkgo/v2" - "github.com/stretchr/testify/require" - - "github.com/ava-labs/avalanchego/api/info" - "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/vms/platformvm" - "github.com/ava-labs/avalanchego/vms/secp256k1fx" -) - -var _ = e2e.DescribePChain("[Permissionless L1]", func() { - tc := e2e.NewTestContext() - require := require.New(tc) - - ginkgo.It("creates a 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) - - pWallet := baseWallet.P() - pClient := platformvm.NewClient(nodeURI.URI) - - owner := &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ - keychain.Keys[0].Address(), - }, - } - - tc.By("issuing a CreateSubnetTx") - 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) - - res, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) - require.NoError(err) - - require.Equal(platformvm.GetSubnetClientResponse{ - IsPermissioned: true, - ControlKeys: []ids.ShortID{ - keychain.Keys[0].Address(), - }, - Threshold: 1, - }, res) - - chainID := ids.GenerateTestID() - address := []byte{'a', 'd', 'd', 'r', 'e', 's', 's'} - - tc.By("issuing a ConvertSubnetTx") - _, err = pWallet.IssueConvertSubnetTx( - subnetID, - chainID, - address, - 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, - ManagerChainID: chainID, - ManagerAddress: address, - }, res) - - _ = e2e.CheckBootstrapIsPossible(tc, env.GetNetwork()) - }) -}) From 8a18b148f717f2078e32c659e5085fd81bcb6e1c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 16:02:38 -0500 Subject: [PATCH 301/400] nit --- tests/e2e/p/l1.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 7466f82323b8..544f970f4f8f 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -16,9 +16,8 @@ import ( "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/vms/example/xsvm/genesis" + "github.com/ava-labs/avalanchego/vms/platformvm" "github.com/ava-labs/avalanchego/vms/secp256k1fx" - - platformvmsdk "github.com/ava-labs/avalanchego/vms/platformvm" ) var _ = e2e.DescribePChain("[L1]", func() { @@ -45,7 +44,7 @@ var _ = e2e.DescribePChain("[L1]", func() { keychain = env.NewKeychain() baseWallet = e2e.NewWallet(tc, keychain, nodeURI) pWallet = baseWallet.P() - pClient = platformvmsdk.NewClient(nodeURI.URI) + pClient = platformvm.NewClient(nodeURI.URI) owner = &secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{ @@ -86,7 +85,7 @@ var _ = e2e.DescribePChain("[L1]", func() { subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) require.NoError(err) require.Equal( - platformvmsdk.GetSubnetClientResponse{ + platformvm.GetSubnetClientResponse{ IsPermissioned: true, ControlKeys: []ids.ShortID{ keychain.Keys[0].Address(), @@ -128,7 +127,7 @@ var _ = e2e.DescribePChain("[L1]", func() { subnet, err := pClient.GetSubnet(tc.DefaultContext(), subnetID) require.NoError(err) require.Equal( - platformvmsdk.GetSubnetClientResponse{ + platformvm.GetSubnetClientResponse{ IsPermissioned: false, ControlKeys: []ids.ShortID{ keychain.Keys[0].Address(), From f8552baee894c602458654df5446fd88439e97b9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 16:18:19 -0500 Subject: [PATCH 302/400] reduce diff --- tests/e2e/p/l1.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 52c3c9b60d39..82e938e9cc2a 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -48,7 +48,7 @@ var _ = e2e.DescribePChain("[L1]", func() { now := time.Now() if !upgrades.IsEtnaActivated(now) { - ginkgo.Skip("Etna is not activated. Permissionless L1s are enabled post-Etna, skipping test.") + ginkgo.Skip("Etna is not activated. L1s are enabled post-Etna, skipping test.") } }) From 5b08c6ad1afe3616ce3477c2bbec075461ba7c51 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 22:25:54 -0500 Subject: [PATCH 303/400] test subnet state prior to conversion --- tests/e2e/p/l1.go | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 82e938e9cc2a..e9c8ec89a6c3 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -124,6 +124,35 @@ var _ = e2e.DescribePChain("[L1]", func() { chainID = chainTx.ID() }) + 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 is configured as expected", func() { + tc.By("verifying the subnet reports as permissioned", func() { + 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, + }, + subnet, + ) + }) + + tc.By("verifying the validator set is empty", func() { + verifyValidatorSet(map[ids.NodeID]*snowvalidators.GetValidatorOutput{}) + }) + }) + tc.By("creating the genesis validator") subnetGenesisNode := e2e.AddEphemeralNode(tc, env.GetNetwork(), tmpnet.FlagsMap{ config.TrackSubnetsKey: subnetID.String(), @@ -154,14 +183,6 @@ var _ = e2e.DescribePChain("[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 an L1", func() { expectedConversionID, err := warpmessage.SubnetConversionID(warpmessage.SubnetConversionData{ SubnetID: subnetID, From 9884a93516ebf75471693175b1a15c29f05c9f3b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 22:36:19 -0500 Subject: [PATCH 304/400] nits --- tests/e2e/p/l1.go | 10 ++++-- .../register-subnet-validator/main.go | 33 +++++++++---------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 5456f4d94e7a..5a8b0fe844e3 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -51,6 +51,11 @@ const ( genesisBalance = units.Avax registerWeight = genesisWeight / 10 registerBalance = 0 + + // Validator registration attempts expire 5 minutes after they are created + expiryDelay = 5 * time.Minute + // P2P message requests timeout after 10 seconds + p2pTimeout = 10 * time.Second ) var _ = e2e.DescribePChain("[L1]", func() { @@ -289,11 +294,12 @@ var _ = e2e.DescribePChain("[L1]", func() { }) tc.By("creating the RegisterSubnetValidatorMessage") + expiry := uint64(time.Now().Add(expiryDelay).Unix()) // This message will expire in 5 minutes registerSubnetValidatorMessage, err := warpmessage.NewRegisterSubnetValidator( subnetID, subnetRegisterNode.NodeID, registerNodePoP.PublicKey, - uint64(time.Now().Add(5*time.Minute).Unix()), + expiry, warpmessage.PChainOwner{}, warpmessage.PChainOwner{}, registerWeight, @@ -379,7 +385,7 @@ func wrapWarpSignatureRequest( logging.NoLog{}, prometheus.NewRegistry(), constants.DefaultNetworkCompressionType, - 10*time.Second, + p2pTimeout, ) if err != nil { return nil, err diff --git a/wallet/subnet/primary/examples/register-subnet-validator/main.go b/wallet/subnet/primary/examples/register-subnet-validator/main.go index 05d51aa1719b..68248a7e18eb 100644 --- a/wallet/subnet/primary/examples/register-subnet-validator/main.go +++ b/wallet/subnet/primary/examples/register-subnet-validator/main.go @@ -8,7 +8,6 @@ import ( "encoding/hex" "encoding/json" "log" - "os" "time" "github.com/ava-labs/avalanchego/api/info" @@ -26,31 +25,27 @@ import ( func main() { key := genesis.EWOQKey - uri := "http://localhost:9710" + uri := primary.LocalAPIURI kc := secp256k1fx.NewKeychain(key) subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") chainID := ids.FromStringOrPanic("2BMFrJ9xeh5JdwZEx6uuFcjfZC2SV2hdbMT8ee5HrvjtfJb5br") - addressHex := "" + address := []byte{} weight := uint64(1) + blsSKHex := "3f783929b295f16cd1172396acb23b20eed057b9afb1caa419e9915f92860b35" - address, err := hex.DecodeString(addressHex) + blsSKBytes, err := hex.DecodeString(blsSKHex) if err != nil { - log.Fatalf("failed to decode address %q: %s\n", addressHex, err) + log.Fatalf("failed to decode secret key: %s\n", err) } - 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) + sk, err := bls.SecretKeyFromBytes(blsSKBytes) if err != nil { log.Fatalf("failed to parse secret key: %s\n", err) } + ctx := context.Background() + infoClient := info.NewClient(uri) + nodeInfoStartTime := time.Now() nodeID, nodePoP, err := infoClient.GetNodeID(ctx) if err != nil { @@ -59,7 +54,7 @@ func main() { 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]. + // [uri] is hosting. walletSyncStartTime := time.Now() wallet, err := primary.MakeWallet(ctx, &primary.WalletConfig{ URI: uri, @@ -75,11 +70,12 @@ func main() { pWallet := wallet.P() context := pWallet.Builder().Context() + expiry := uint64(time.Now().Add(5 * time.Minute).Unix()) // This message will expire in 5 minutes addressedCallPayload, err := message.NewRegisterSubnetValidator( subnetID, nodeID, nodePoP.PublicKey, - uint64(time.Now().Add(5*time.Minute).Unix()), + expiry, message.PChainOwner{}, message.PChainOwner{}, weight, @@ -110,8 +106,9 @@ 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]] + // This example assumes that the hard-coded BLS key is for the first + // validator in the signature bit-set. + signers := set.NewBits(0) unsignedBytes := unsignedWarp.Bytes() sig := bls.Sign(sk, unsignedBytes) From a7cd427491320bf36016e7ac68bdd273c2a04928 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 5 Nov 2024 22:49:18 -0500 Subject: [PATCH 305/400] nit --- tests/e2e/p/l1.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 5a8b0fe844e3..6ee961450a34 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -333,8 +333,7 @@ var _ = e2e.DescribePChain("[L1]", func() { 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 + signers := set.NewBits(0) // [signers] has weight from the genesis peer var sigBytes [bls.SignatureLen]byte copy(sigBytes[:], bls.SignatureToBytes(registerSubnetValidatorSignature)) From 4c2605c6210402c2cb96e181d0916313373a2cc3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 6 Nov 2024 01:06:38 -0500 Subject: [PATCH 306/400] wip --- vms/platformvm/block/executor/verifier.go | 31 +- .../block/executor/verifier_test.go | 98 +++++++ vms/platformvm/metrics/tx_metrics.go | 8 +- .../txs/executor/atomic_tx_executor.go | 53 +++- .../txs/executor/standard_tx_executor.go | 270 +++++++++--------- vms/platformvm/txs/fee/complexity.go | 148 +++++----- vms/platformvm/txs/fee/static_calculator.go | 22 +- vms/platformvm/txs/visitor.go | 9 +- wallet/chain/p/signer/visitor.go | 32 +-- wallet/chain/p/wallet/backend_visitor.go | 40 +-- 10 files changed, 419 insertions(+), 292 deletions(-) diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index 1570a1b3c2ac..24d2083d2ae3 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -230,23 +230,22 @@ func (v *verifier) ApricotAtomicBlock(b *block.ApricotAtomicBlock) error { } feeCalculator := state.NewStaticFeeCalculator(v.txExecutorBackend.Config, currentTimestamp) - atomicExecutor := executor.AtomicTxExecutor{ - Backend: v.txExecutorBackend, - FeeCalculator: feeCalculator, - ParentID: parentID, - StateVersions: v, - Tx: b.Tx, - } - - if err := b.Tx.Unsigned.Visit(&atomicExecutor); err != nil { + onAcceptState, atomicInputs, atomicRequests, err := executor.AtomicTx( + v.txExecutorBackend, + feeCalculator, + parentID, + v, + b.Tx, + ) + if err != nil { txID := b.Tx.ID() v.MarkDropped(txID, err) // cache tx as dropped - return fmt.Errorf("tx %s failed semantic verification: %w", txID, err) + return err } - atomicExecutor.OnAccept.AddTx(b.Tx, status.Committed) + onAcceptState.AddTx(b.Tx, status.Committed) - if err := v.verifyUniqueInputs(parentID, atomicExecutor.Inputs); err != nil { + if err := v.verifyUniqueInputs(parentID, atomicInputs); err != nil { return err } @@ -256,11 +255,11 @@ func (v *verifier) ApricotAtomicBlock(b *block.ApricotAtomicBlock) error { v.blkIDToState[blkID] = &blockState{ statelessBlock: b, - onAcceptState: atomicExecutor.OnAccept, + onAcceptState: onAcceptState, - inputs: atomicExecutor.Inputs, - timestamp: atomicExecutor.OnAccept.GetTimestamp(), - atomicRequests: atomicExecutor.AtomicRequests, + inputs: atomicInputs, + timestamp: onAcceptState.GetTimestamp(), + atomicRequests: atomicRequests, verifiedHeights: set.Of(v.pChainHeight), } return nil diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 0a5d9e3c662f..db8e3761f3d1 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -182,6 +182,104 @@ func TestVerifierVisitProposalBlock(t *testing.T) { require.NoError(blk.Verify(context.Background())) } +func TestVerifierVisitAtomicBlock2(t *testing.T) { + s := statetest.New(t, statetest.Config{}) + verifier := newTestVerifier(t, s) + wallet := txstest.NewWallet( + t, + verifier.ctx, + verifier.txExecutorBackend.Config, + s, + secp256k1fx.NewKeychain(genesis.EWOQKey), + nil, // subnetIDs + nil, // chainIDs + ) + + baseTx0, err := wallet.IssueBaseTx([]*avax.TransferableOutput{}) + require.NoError(t, err) + baseTx1, err := wallet.IssueBaseTx([]*avax.TransferableOutput{}) + require.NoError(t, err) + + blockComplexity, err := txfee.TxComplexity(baseTx0.Unsigned, baseTx1.Unsigned) + require.NoError(t, err) + blockGas, err := blockComplexity.ToGas(verifier.txExecutorBackend.Config.DynamicFeeConfig.Weights) + require.NoError(t, err) + + const secondsToAdvance = 10 + + initialFeeState := gas.State{} + feeStateAfterTimeAdvanced := initialFeeState.AdvanceTime( + verifier.txExecutorBackend.Config.DynamicFeeConfig.MaxCapacity, + verifier.txExecutorBackend.Config.DynamicFeeConfig.MaxPerSecond, + verifier.txExecutorBackend.Config.DynamicFeeConfig.TargetPerSecond, + secondsToAdvance, + ) + feeStateAfterGasConsumed, err := feeStateAfterTimeAdvanced.ConsumeGas(blockGas) + require.NoError(t, err) + + tests := []struct { + name string + timestamp time.Time + expectedErr error + expectedFeeState gas.State + }{ + { + name: "no capacity", + timestamp: genesistest.DefaultValidatorStartTime, + expectedErr: gas.ErrInsufficientCapacity, + }, + { + name: "updates fee state", + timestamp: genesistest.DefaultValidatorStartTime.Add(secondsToAdvance * time.Second), + expectedFeeState: feeStateAfterGasConsumed, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + // Clear the state to prevent prior tests from impacting this test. + clear(verifier.blkIDToState) + + verifier.txExecutorBackend.Clk.Set(test.timestamp) + timestamp, _, err := state.NextBlockTime( + verifier.txExecutorBackend.Config.ValidatorFeeConfig, + s, + verifier.txExecutorBackend.Clk, + ) + require.NoError(err) + + lastAcceptedID := s.GetLastAccepted() + lastAccepted, err := s.GetStatelessBlock(lastAcceptedID) + require.NoError(err) + + blk, err := block.NewBanffStandardBlock( + timestamp, + lastAcceptedID, + lastAccepted.Height()+1, + []*txs.Tx{ + baseTx0, + baseTx1, + }, + ) + require.NoError(err) + + blkID := blk.ID() + err = blk.Visit(verifier) + require.ErrorIs(err, test.expectedErr) + if err != nil { + require.NotContains(verifier.blkIDToState, blkID) + return + } + + require.Contains(verifier.blkIDToState, blkID) + blockState := verifier.blkIDToState[blkID] + require.Equal(blk, blockState.statelessBlock) + require.Equal(test.expectedFeeState, blockState.onAcceptState.GetFeeState()) + }) + } +} + func TestVerifierVisitAtomicBlock(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) diff --git a/vms/platformvm/metrics/tx_metrics.go b/vms/platformvm/metrics/tx_metrics.go index fd6c63494f7c..7957cb6ab2e9 100644 --- a/vms/platformvm/metrics/tx_metrics.go +++ b/vms/platformvm/metrics/tx_metrics.go @@ -132,16 +132,16 @@ func (m *txMetrics) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) er return nil } -func (m *txMetrics) ConvertSubnetTx(*txs.ConvertSubnetTx) error { +func (m *txMetrics) BaseTx(*txs.BaseTx) error { m.numTxs.With(prometheus.Labels{ - txLabel: "convert_subnet", + txLabel: "base", }).Inc() return nil } -func (m *txMetrics) BaseTx(*txs.BaseTx) error { +func (m *txMetrics) ConvertSubnetTx(*txs.ConvertSubnetTx) error { m.numTxs.With(prometheus.Labels{ - txLabel: "base", + txLabel: "convert_subnet", }).Inc() return nil } diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index 190c5ec114c9..c1a4b7b54627 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -4,6 +4,8 @@ package executor import ( + "fmt" + "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/set" @@ -18,16 +20,37 @@ var _ txs.Visitor = (*AtomicTxExecutor)(nil) // the execution was moved to be performed inside of the standardTxExecutor. type AtomicTxExecutor struct { // inputs, to be filled before visitor methods are called - *Backend - FeeCalculator fee.Calculator - ParentID ids.ID - StateVersions state.Versions - Tx *txs.Tx + backend *Backend + feeCalculator fee.Calculator + parentID ids.ID + stateVersions state.Versions + tx *txs.Tx // outputs of visitor execution OnAccept state.Diff Inputs set.Set[ids.ID] - AtomicRequests map[ids.ID]*atomic.Requests + atomicRequests map[ids.ID]*atomic.Requests +} + +func AtomicTx( + backend *Backend, + feeCalculator fee.Calculator, + parentID ids.ID, + stateVersions state.Versions, + tx *txs.Tx, +) (state.Diff, set.Set[ids.ID], map[ids.ID]*atomic.Requests, error) { + atomicExecutor := AtomicTxExecutor{ + backend: backend, + feeCalculator: feeCalculator, + parentID: parentID, + stateVersions: stateVersions, + tx: tx, + } + if err := tx.Unsigned.Visit(&atomicExecutor); err != nil { + txID := tx.ID() + return nil, nil, nil, fmt.Errorf("atomic tx %s failed execution: %w", txID, err) + } + return atomicExecutor.OnAccept, atomicExecutor.Inputs, atomicExecutor.atomicRequests, nil } func (*AtomicTxExecutor) AddValidatorTx(*txs.AddValidatorTx) error { @@ -66,15 +89,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 } @@ -96,8 +119,8 @@ func (e *AtomicTxExecutor) ExportTx(tx *txs.ExportTx) error { func (e *AtomicTxExecutor) atomicTx(tx txs.UnsignedTx) error { onAccept, err := state.NewDiff( - e.ParentID, - e.StateVersions, + e.parentID, + e.stateVersions, ) if err != nil { return err @@ -105,13 +128,13 @@ func (e *AtomicTxExecutor) atomicTx(tx txs.UnsignedTx) error { e.OnAccept = onAccept executor := StandardTxExecutor{ - Backend: e.Backend, + Backend: e.backend, State: e.OnAccept, - FeeCalculator: e.FeeCalculator, - Tx: e.Tx, + FeeCalculator: e.feeCalculator, + Tx: e.tx, } err = tx.Visit(&executor) e.Inputs = executor.Inputs - e.AtomicRequests = executor.AtomicRequests + e.atomicRequests = executor.AtomicRequests return err } diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 1c08880a94e0..1136f60e6a1d 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -53,6 +53,82 @@ func (*StandardTxExecutor) RewardValidatorTx(*txs.RewardValidatorTx) error { return ErrWrongTxType } +func (e *StandardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { + if tx.Validator.NodeID == ids.EmptyNodeID { + return errEmptyNodeID + } + + if _, err := verifyAddValidatorTx( + e.Backend, + e.FeeCalculator, + e.State, + e.Tx, + tx, + ); err != nil { + return err + } + + if err := e.putStaker(tx); err != nil { + return err + } + + txID := e.Tx.ID() + avax.Consume(e.State, tx.Ins) + avax.Produce(e.State, txID, tx.Outs) + + if e.Config.PartialSyncPrimaryNetwork && tx.Validator.NodeID == e.Ctx.NodeID { + e.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", + zap.String("reason", "primary network is not being fully synced"), + zap.Stringer("txID", txID), + zap.String("txType", "addValidator"), + zap.Stringer("nodeID", tx.Validator.NodeID), + ) + } + return nil +} + +func (e *StandardTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { + if err := verifyAddSubnetValidatorTx( + e.Backend, + e.FeeCalculator, + e.State, + e.Tx, + tx, + ); err != nil { + return err + } + + if err := e.putStaker(tx); err != nil { + return err + } + + txID := e.Tx.ID() + avax.Consume(e.State, tx.Ins) + avax.Produce(e.State, txID, tx.Outs) + return nil +} + +func (e *StandardTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { + if _, err := verifyAddDelegatorTx( + e.Backend, + e.FeeCalculator, + e.State, + e.Tx, + tx, + ); err != nil { + return err + } + + if err := e.putStaker(tx); err != nil { + return err + } + + txID := e.Tx.ID() + avax.Consume(e.State, tx.Ins) + avax.Produce(e.State, txID, tx.Outs) + return nil +} + func (e *StandardTxExecutor) CreateChainTx(tx *txs.CreateChainTx) error { if err := e.Tx.SyntacticVerify(e.Ctx); err != nil { return err @@ -326,82 +402,6 @@ func (e *StandardTxExecutor) ExportTx(tx *txs.ExportTx) error { return nil } -func (e *StandardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { - if tx.Validator.NodeID == ids.EmptyNodeID { - return errEmptyNodeID - } - - if _, err := verifyAddValidatorTx( - e.Backend, - e.FeeCalculator, - e.State, - e.Tx, - tx, - ); err != nil { - return err - } - - if err := e.putStaker(tx); err != nil { - return err - } - - txID := e.Tx.ID() - avax.Consume(e.State, tx.Ins) - avax.Produce(e.State, txID, tx.Outs) - - if e.Config.PartialSyncPrimaryNetwork && tx.Validator.NodeID == e.Ctx.NodeID { - e.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", - zap.String("reason", "primary network is not being fully synced"), - zap.Stringer("txID", txID), - zap.String("txType", "addValidator"), - zap.Stringer("nodeID", tx.Validator.NodeID), - ) - } - return nil -} - -func (e *StandardTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { - if err := verifyAddSubnetValidatorTx( - e.Backend, - e.FeeCalculator, - e.State, - e.Tx, - tx, - ); err != nil { - return err - } - - if err := e.putStaker(tx); err != nil { - return err - } - - txID := e.Tx.ID() - avax.Consume(e.State, tx.Ins) - avax.Produce(e.State, txID, tx.Outs) - return nil -} - -func (e *StandardTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { - if _, err := verifyAddDelegatorTx( - e.Backend, - e.FeeCalculator, - e.State, - e.Tx, - tx, - ); err != nil { - return err - } - - if err := e.putStaker(tx); err != nil { - return err - } - - txID := e.Tx.ID() - avax.Consume(e.State, tx.Ins) - avax.Produce(e.State, txID, tx.Outs) - return nil -} - // Verifies a [*txs.RemoveSubnetValidatorTx] and, if it passes, executes it on // [e.State]. For verification rules, see [verifyRemoveSubnetValidatorTx]. This // transaction will result in [tx.NodeID] being removed as a validator of @@ -495,65 +495,6 @@ func (e *StandardTxExecutor) TransformSubnetTx(tx *txs.TransformSubnetTx) error return nil } -func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) 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 - } - - baseTxCreds, err := verifyPoASubnetAuthorization(e.Backend, e.State, e.Tx, tx.Subnet, tx.SubnetAuth) - 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) - // Track the subnet conversion in the database - e.State.SetSubnetConversion( - tx.Subnet, - state.SubnetConversion{ - // TODO: Populate the conversionID - ConversionID: ids.Empty, - ChainID: tx.ChainID, - Addr: tx.Address, - }, - ) - return nil -} - func (e *StandardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { if err := verifyAddPermissionlessValidatorTx( e.Backend, @@ -676,6 +617,65 @@ func (e *StandardTxExecutor) BaseTx(tx *txs.BaseTx) error { return nil } +func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) 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 + } + + baseTxCreds, err := verifyPoASubnetAuthorization(e.Backend, e.State, e.Tx, tx.Subnet, tx.SubnetAuth) + 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) + // Track the subnet conversion in the database + e.State.SetSubnetConversion( + tx.Subnet, + state.SubnetConversion{ + // TODO: Populate the conversionID + ConversionID: ids.Empty, + ChainID: tx.ChainID, + Addr: tx.Address, + }, + ) + return nil +} + // Creates the staker as defined in [stakerTx] and adds it to [e.State]. func (e *StandardTxExecutor) putStaker(stakerTx txs.Staker) error { var ( diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 02942e09b76f..59dea8b6a600 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -383,11 +383,11 @@ type complexityVisitor struct { output gas.Dimensions } -func (*complexityVisitor) AddDelegatorTx(*txs.AddDelegatorTx) error { +func (*complexityVisitor) AddValidatorTx(*txs.AddValidatorTx) error { return ErrUnsupportedTx } -func (*complexityVisitor) AddValidatorTx(*txs.AddValidatorTx) error { +func (*complexityVisitor) AddDelegatorTx(*txs.AddDelegatorTx) error { return ErrUnsupportedTx } @@ -403,60 +403,6 @@ func (*complexityVisitor) TransformSubnetTx(*txs.TransformSubnetTx) error { return ErrUnsupportedTx } -func (c *complexityVisitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { - // TODO: Should we include additional complexity for subnets? - baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) - if err != nil { - return err - } - signerComplexity, err := SignerComplexity(tx.Signer) - if err != nil { - return err - } - outputsComplexity, err := OutputComplexity(tx.StakeOuts...) - if err != nil { - return err - } - validatorOwnerComplexity, err := OwnerComplexity(tx.ValidatorRewardsOwner) - if err != nil { - return err - } - delegatorOwnerComplexity, err := OwnerComplexity(tx.DelegatorRewardsOwner) - if err != nil { - return err - } - c.output, err = IntrinsicAddPermissionlessValidatorTxComplexities.Add( - &baseTxComplexity, - &signerComplexity, - &outputsComplexity, - &validatorOwnerComplexity, - &delegatorOwnerComplexity, - ) - return err -} - -func (c *complexityVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { - // TODO: Should we include additional complexity for subnets? - baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) - if err != nil { - return err - } - ownerComplexity, err := OwnerComplexity(tx.DelegationRewardsOwner) - if err != nil { - return err - } - outputsComplexity, err := OutputComplexity(tx.StakeOuts...) - if err != nil { - return err - } - c.output, err = IntrinsicAddPermissionlessDelegatorTxComplexities.Add( - &baseTxComplexity, - &ownerComplexity, - &outputsComplexity, - ) - return err -} - func (c *complexityVisitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { @@ -473,15 +419,6 @@ func (c *complexityVisitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) e return err } -func (c *complexityVisitor) BaseTx(tx *txs.BaseTx) error { - baseTxComplexity, err := baseTxComplexity(tx) - if err != nil { - return err - } - c.output, err = IntrinsicBaseTxComplexities.Add(&baseTxComplexity) - return err -} - func (c *complexityVisitor) CreateChainTx(tx *txs.CreateChainTx) error { bandwidth, err := math.Mul(uint64(len(tx.FxIDs)), ids.IDLen) if err != nil { @@ -534,6 +471,23 @@ func (c *complexityVisitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { return err } +func (c *complexityVisitor) ImportTx(tx *txs.ImportTx) error { + baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) + if err != nil { + return err + } + // TODO: Should imported inputs be more complex? + inputsComplexity, err := InputComplexity(tx.ImportedInputs...) + if err != nil { + return err + } + c.output, err = IntrinsicImportTxComplexities.Add( + &baseTxComplexity, + &inputsComplexity, + ) + return err +} + func (c *complexityVisitor) ExportTx(tx *txs.ExportTx) error { baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { @@ -551,35 +505,72 @@ func (c *complexityVisitor) ExportTx(tx *txs.ExportTx) error { return err } -func (c *complexityVisitor) ImportTx(tx *txs.ImportTx) error { +func (c *complexityVisitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { return err } - // TODO: Should imported inputs be more complex? - inputsComplexity, err := InputComplexity(tx.ImportedInputs...) + authComplexity, err := AuthComplexity(tx.SubnetAuth) if err != nil { return err } - c.output, err = IntrinsicImportTxComplexities.Add( + c.output, err = IntrinsicRemoveSubnetValidatorTxComplexities.Add( &baseTxComplexity, - &inputsComplexity, + &authComplexity, ) return err } -func (c *complexityVisitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { +func (c *complexityVisitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { + // TODO: Should we include additional complexity for subnets? baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { return err } - authComplexity, err := AuthComplexity(tx.SubnetAuth) + signerComplexity, err := SignerComplexity(tx.Signer) if err != nil { return err } - c.output, err = IntrinsicRemoveSubnetValidatorTxComplexities.Add( + outputsComplexity, err := OutputComplexity(tx.StakeOuts...) + if err != nil { + return err + } + validatorOwnerComplexity, err := OwnerComplexity(tx.ValidatorRewardsOwner) + if err != nil { + return err + } + delegatorOwnerComplexity, err := OwnerComplexity(tx.DelegatorRewardsOwner) + if err != nil { + return err + } + c.output, err = IntrinsicAddPermissionlessValidatorTxComplexities.Add( &baseTxComplexity, - &authComplexity, + &signerComplexity, + &outputsComplexity, + &validatorOwnerComplexity, + &delegatorOwnerComplexity, + ) + return err +} + +func (c *complexityVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { + // TODO: Should we include additional complexity for subnets? + baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) + if err != nil { + return err + } + ownerComplexity, err := OwnerComplexity(tx.DelegationRewardsOwner) + if err != nil { + return err + } + outputsComplexity, err := OutputComplexity(tx.StakeOuts...) + if err != nil { + return err + } + c.output, err = IntrinsicAddPermissionlessDelegatorTxComplexities.Add( + &baseTxComplexity, + &ownerComplexity, + &outputsComplexity, ) return err } @@ -605,6 +596,15 @@ func (c *complexityVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwne return err } +func (c *complexityVisitor) BaseTx(tx *txs.BaseTx) error { + baseTxComplexity, err := baseTxComplexity(tx) + if err != nil { + return err + } + c.output, err = IntrinsicBaseTxComplexities.Add(&baseTxComplexity) + return err +} + func (c *complexityVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { diff --git a/vms/platformvm/txs/fee/static_calculator.go b/vms/platformvm/txs/fee/static_calculator.go index 888ccba8621c..1b97349ce2cb 100644 --- a/vms/platformvm/txs/fee/static_calculator.go +++ b/vms/platformvm/txs/fee/static_calculator.go @@ -76,21 +76,26 @@ func (c *staticVisitor) CreateSubnetTx(*txs.CreateSubnetTx) error { return nil } -func (c *staticVisitor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { +func (c *staticVisitor) ImportTx(*txs.ImportTx) error { c.fee = c.config.TxFee return nil } -func (c *staticVisitor) TransformSubnetTx(*txs.TransformSubnetTx) error { - c.fee = c.config.TransformSubnetTxFee +func (c *staticVisitor) ExportTx(*txs.ExportTx) error { + c.fee = c.config.TxFee return nil } -func (c *staticVisitor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { +func (c *staticVisitor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { c.fee = c.config.TxFee return nil } +func (c *staticVisitor) TransformSubnetTx(*txs.TransformSubnetTx) error { + c.fee = c.config.TransformSubnetTxFee + return nil +} + func (c *staticVisitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { if tx.Subnet != constants.PrimaryNetworkID { c.fee = c.config.AddSubnetValidatorFee @@ -109,17 +114,12 @@ func (c *staticVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDe return nil } -func (c *staticVisitor) BaseTx(*txs.BaseTx) error { - c.fee = c.config.TxFee - return nil -} - -func (c *staticVisitor) ImportTx(*txs.ImportTx) error { +func (c *staticVisitor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { c.fee = c.config.TxFee return nil } -func (c *staticVisitor) ExportTx(*txs.ExportTx) error { +func (c *staticVisitor) BaseTx(*txs.BaseTx) error { c.fee = c.config.TxFee return nil } diff --git a/vms/platformvm/txs/visitor.go b/vms/platformvm/txs/visitor.go index 142a6115d7af..21c46476fa5f 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,17 @@ 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 } diff --git a/wallet/chain/p/signer/visitor.go b/wallet/chain/p/signer/visitor.go index 38d501c908b0..b358e1f5d5ea 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -51,14 +51,6 @@ func (*visitor) RewardValidatorTx(*txs.RewardValidatorTx) error { return ErrUnsupportedTxType } -func (s *visitor) BaseTx(tx *txs.BaseTx) error { - txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) - if err != nil { - return err - } - return sign(s.tx, false, txSigners) -} - func (s *visitor) AddValidatorTx(tx *txs.AddValidatorTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { @@ -143,7 +135,7 @@ func (s *visitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error return sign(s.tx, true, txSigners) } -func (s *visitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { +func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err @@ -156,20 +148,23 @@ func (s *visitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) e return sign(s.tx, true, txSigners) } -func (s *visitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { +func (s *visitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err } - subnetAuthSigners, err := s.getSubnetSigners(tx.Subnet, tx.SubnetAuth) + return sign(s.tx, true, txSigners) +} + +func (s *visitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { + txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err } - txSigners = append(txSigners, subnetAuthSigners) return sign(s.tx, true, txSigners) } -func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { +func (s *visitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err @@ -182,19 +177,24 @@ func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { return sign(s.tx, true, txSigners) } -func (s *visitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { +func (s *visitor) BaseTx(tx *txs.BaseTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err } - return sign(s.tx, true, txSigners) + return sign(s.tx, false, txSigners) } -func (s *visitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { +func (s *visitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err } + subnetAuthSigners, err := s.getSubnetSigners(tx.Subnet, tx.SubnetAuth) + if err != nil { + return err + } + txSigners = append(txSigners, subnetAuthSigners) return sign(s.tx, true, txSigners) } diff --git a/wallet/chain/p/wallet/backend_visitor.go b/wallet/chain/p/wallet/backend_visitor.go index 8e44ee3b5e2d..f2f9e646edf8 100644 --- a/wallet/chain/p/wallet/backend_visitor.go +++ b/wallet/chain/p/wallet/backend_visitor.go @@ -58,26 +58,6 @@ func (b *backendVisitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { return b.baseTx(&tx.BaseTx) } -func (b *backendVisitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { - return b.baseTx(&tx.BaseTx) -} - -func (b *backendVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { - b.b.setSubnetOwner( - tx.Subnet, - tx.Owner, - ) - return b.baseTx(&tx.BaseTx) -} - -func (b *backendVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { - return b.baseTx(&tx.BaseTx) -} - -func (b *backendVisitor) BaseTx(tx *txs.BaseTx) error { - return b.baseTx(tx) -} - func (b *backendVisitor) ImportTx(tx *txs.ImportTx) error { err := b.b.removeUTXOs( b.ctx, @@ -111,6 +91,10 @@ func (b *backendVisitor) ExportTx(tx *txs.ExportTx) error { return b.baseTx(&tx.BaseTx) } +func (b *backendVisitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { + return b.baseTx(&tx.BaseTx) +} + func (b *backendVisitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { return b.baseTx(&tx.BaseTx) } @@ -123,6 +107,22 @@ func (b *backendVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessD return b.baseTx(&tx.BaseTx) } +func (b *backendVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { + b.b.setSubnetOwner( + tx.Subnet, + tx.Owner, + ) + return b.baseTx(&tx.BaseTx) +} + +func (b *backendVisitor) BaseTx(tx *txs.BaseTx) error { + return b.baseTx(tx) +} + +func (b *backendVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { + return b.baseTx(&tx.BaseTx) +} + func (b *backendVisitor) baseTx(tx *txs.BaseTx) error { return b.b.removeUTXOs( b.ctx, From 436b471c161c69de30136bb5477966fb65dcf566 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 6 Nov 2024 01:23:30 -0500 Subject: [PATCH 307/400] Remove unused proto field --- proto/pb/platformvm/platformvm.pb.go | 31 ++++++++++------------------ proto/platformvm/platformvm.proto | 1 - 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/proto/pb/platformvm/platformvm.pb.go b/proto/pb/platformvm/platformvm.pb.go index 10b40671a881..15c2e96f37f2 100644 --- a/proto/pb/platformvm/platformvm.pb.go +++ b/proto/pb/platformvm/platformvm.pb.go @@ -30,7 +30,6 @@ type SubnetValidatorRegistrationJustification struct { // *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() { @@ -86,13 +85,6 @@ func (x *SubnetValidatorRegistrationJustification) GetRegisterSubnetValidatorMes return nil } -func (x *SubnetValidatorRegistrationJustification) GetFilter() []byte { - if x != nil { - return x.Filter - } - return nil -} - type isSubnetValidatorRegistrationJustification_Preimage interface { isSubnetValidatorRegistrationJustification_Preimage() } @@ -174,7 +166,7 @@ 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, + 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x76, 0x6d, 0x22, 0xd5, 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, @@ -187,17 +179,16 @@ var file_platformvm_platformvm_proto_rawDesc = []byte{ 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, + 0x73, 0x73, 0x61, 0x67, 0x65, 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 ( diff --git a/proto/platformvm/platformvm.proto b/proto/platformvm/platformvm.proto index a1e9adf129d1..571be0d7862b 100644 --- a/proto/platformvm/platformvm.proto +++ b/proto/platformvm/platformvm.proto @@ -12,7 +12,6 @@ message SubnetValidatorRegistrationJustification { // The SubnetValidator is being removed from the Subnet bytes register_subnet_validator_message = 2; } - bytes filter = 3; } message SubnetIDIndex { From acf026aab812929f65914a4b18ca33f82df33453 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 6 Nov 2024 12:09:43 -0500 Subject: [PATCH 308/400] comments --- vms/platformvm/txs/executor/atomic_tx_executor.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index c1a4b7b54627..317068c97893 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -16,8 +16,6 @@ import ( var _ txs.Visitor = (*AtomicTxExecutor)(nil) -// atomicTxExecutor is used to execute atomic transactions pre-AP5. After AP5 -// the execution was moved to be performed inside of the standardTxExecutor. type AtomicTxExecutor struct { // inputs, to be filled before visitor methods are called backend *Backend @@ -32,6 +30,11 @@ type AtomicTxExecutor struct { atomicRequests map[ids.ID]*atomic.Requests } +// AtomicTx executes the atomic transaction [tx] and returns the resulting state +// modifications. +// +// This is only used to execute atomic transactions pre-AP5. After AP5 the +// execution was moved to be performed during standard transaction execution. func AtomicTx( backend *Backend, feeCalculator fee.Calculator, From 0bf397a3cdb763e50e83013005c5d24e2aeba8dc Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 6 Nov 2024 13:23:48 -0500 Subject: [PATCH 309/400] Unexport AtomicTxExecutor --- .../block/executor/verifier_test.go | 285 +++++++----------- .../txs/executor/atomic_tx_executor.go | 74 ++--- 2 files changed, 143 insertions(+), 216 deletions(-) diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index db8e3761f3d1..1a89a0bbda4c 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -18,6 +18,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/snowtest" + "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/avalanchego/upgrade/upgradetest" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" @@ -49,17 +50,27 @@ import ( validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) -func newTestVerifier(t testing.TB, s state.State) *verifier { +type testVerifierConfig struct { + Upgrades upgrade.Config +} + +func newTestVerifier(t testing.TB, c testVerifierConfig) *verifier { require := require.New(t) + if c.Upgrades == (upgrade.Config{}) { + c.Upgrades = upgradetest.GetConfig(upgradetest.Latest) + } + mempool, err := mempool.New("", prometheus.NewRegistry(), nil) require.NoError(err) var ( - upgrades = upgradetest.GetConfig(upgradetest.Latest) - ctx = snowtest.Context(t, constants.PlatformChainID) - clock = &mockable.Clock{} - fx = &secp256k1fx.Fx{} + state = statetest.New(t, statetest.Config{ + Upgrades: c.Upgrades, + }) + ctx = snowtest.Context(t, constants.PlatformChainID) + clock = &mockable.Clock{} + fx = &secp256k1fx.Fx{} ) require.NoError(fx.InitializeVM(&secp256k1fx.TestVM{ Clk: *clock, @@ -69,9 +80,9 @@ func newTestVerifier(t testing.TB, s state.State) *verifier { return &verifier{ backend: &backend{ Mempool: mempool, - lastAccepted: s.GetLastAccepted(), + lastAccepted: state.GetLastAccepted(), blkIDToState: make(map[ids.ID]*blockState), - state: s, + state: state, ctx: ctx, }, txExecutorBackend: &executor.Backend{ @@ -81,7 +92,7 @@ func newTestVerifier(t testing.TB, s state.State) *verifier { DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, SybilProtectionEnabled: true, - UpgradeConfig: upgrades, + UpgradeConfig: c.Upgrades, }, Ctx: ctx, Clk: clock, @@ -182,185 +193,102 @@ func TestVerifierVisitProposalBlock(t *testing.T) { require.NoError(blk.Verify(context.Background())) } -func TestVerifierVisitAtomicBlock2(t *testing.T) { - s := statetest.New(t, statetest.Config{}) - verifier := newTestVerifier(t, s) - wallet := txstest.NewWallet( - t, - verifier.ctx, - verifier.txExecutorBackend.Config, - s, - secp256k1fx.NewKeychain(genesis.EWOQKey), - nil, // subnetIDs - nil, // chainIDs - ) - - baseTx0, err := wallet.IssueBaseTx([]*avax.TransferableOutput{}) - require.NoError(t, err) - baseTx1, err := wallet.IssueBaseTx([]*avax.TransferableOutput{}) - require.NoError(t, err) - - blockComplexity, err := txfee.TxComplexity(baseTx0.Unsigned, baseTx1.Unsigned) - require.NoError(t, err) - blockGas, err := blockComplexity.ToGas(verifier.txExecutorBackend.Config.DynamicFeeConfig.Weights) - require.NoError(t, err) - - const secondsToAdvance = 10 - - initialFeeState := gas.State{} - feeStateAfterTimeAdvanced := initialFeeState.AdvanceTime( - verifier.txExecutorBackend.Config.DynamicFeeConfig.MaxCapacity, - verifier.txExecutorBackend.Config.DynamicFeeConfig.MaxPerSecond, - verifier.txExecutorBackend.Config.DynamicFeeConfig.TargetPerSecond, - secondsToAdvance, +func TestVerifierVisitAtomicBlock(t *testing.T) { + var ( + require = require.New(t) + verifier = newTestVerifier(t, testVerifierConfig{ + Upgrades: upgradetest.GetConfig(upgradetest.ApricotPhase4), + }) + wallet = txstest.NewWallet( + t, + verifier.ctx, + verifier.txExecutorBackend.Config, + verifier.state, + secp256k1fx.NewKeychain(genesis.EWOQKey), + nil, // subnetIDs + nil, // chainIDs + ) + exportedOutput = &avax.TransferableOutput{ + Asset: avax.Asset{ID: verifier.ctx.AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: units.NanoAvax, + OutputOwners: secp256k1fx.OutputOwners{}, + }, + } + initialTimestamp = verifier.state.GetTimestamp() ) - feeStateAfterGasConsumed, err := feeStateAfterTimeAdvanced.ConsumeGas(blockGas) - require.NoError(t, err) - tests := []struct { - name string - timestamp time.Time - expectedErr error - expectedFeeState gas.State - }{ - { - name: "no capacity", - timestamp: genesistest.DefaultValidatorStartTime, - expectedErr: gas.ErrInsufficientCapacity, - }, - { - name: "updates fee state", - timestamp: genesistest.DefaultValidatorStartTime.Add(secondsToAdvance * time.Second), - expectedFeeState: feeStateAfterGasConsumed, + // Build the transaction that will be executed. + atomicTx, err := wallet.IssueExportTx( + verifier.ctx.XChainID, + []*avax.TransferableOutput{ + exportedOutput, }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - // Clear the state to prevent prior tests from impacting this test. - clear(verifier.blkIDToState) - - verifier.txExecutorBackend.Clk.Set(test.timestamp) - timestamp, _, err := state.NextBlockTime( - verifier.txExecutorBackend.Config.ValidatorFeeConfig, - s, - verifier.txExecutorBackend.Clk, - ) - require.NoError(err) - - lastAcceptedID := s.GetLastAccepted() - lastAccepted, err := s.GetStatelessBlock(lastAcceptedID) - require.NoError(err) + ) + require.NoError(err) - blk, err := block.NewBanffStandardBlock( - timestamp, - lastAcceptedID, - lastAccepted.Height()+1, - []*txs.Tx{ - baseTx0, - baseTx1, - }, - ) - require.NoError(err) + // Build the block that will be executed on top of the last accepted block. + lastAcceptedID := verifier.state.GetLastAccepted() + lastAccepted, err := verifier.state.GetStatelessBlock(lastAcceptedID) + require.NoError(err) - blkID := blk.ID() - err = blk.Visit(verifier) - require.ErrorIs(err, test.expectedErr) - if err != nil { - require.NotContains(verifier.blkIDToState, blkID) - return - } + atomicBlock, err := block.NewApricotAtomicBlock( + lastAcceptedID, + lastAccepted.Height()+1, + atomicTx, + ) + require.NoError(err) - require.Contains(verifier.blkIDToState, blkID) - blockState := verifier.blkIDToState[blkID] - require.Equal(blk, blockState.statelessBlock) - require.Equal(test.expectedFeeState, blockState.onAcceptState.GetFeeState()) - }) - } -} + // Execute the block. + require.NoError(atomicBlock.Visit(verifier)) -func TestVerifierVisitAtomicBlock(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) + // Verify that the block's execution was recorded as expected. + blkID := atomicBlock.ID() + require.Contains(verifier.blkIDToState, blkID) + atomicBlockState := verifier.blkIDToState[blkID] + onAccept := atomicBlockState.onAcceptState + require.NotNil(onAccept) - // Create mocked dependencies. - s := state.NewMockState(ctrl) - mempool := mempoolmock.NewMempool(ctrl) - parentID := ids.GenerateTestID() - parentStatelessBlk := block.NewMockBlock(ctrl) - grandparentID := ids.GenerateTestID() - parentState := state.NewMockDiff(ctrl) + txID := atomicTx.ID() + acceptedTx, acceptedStatus, err := onAccept.GetTx(txID) + require.NoError(err) + require.Equal(atomicTx, acceptedTx) + require.Equal(status.Committed, acceptedStatus) - backend := &backend{ - blkIDToState: map[ids.ID]*blockState{ - parentID: { - statelessBlock: parentStatelessBlk, - onAcceptState: parentState, - }, - }, - Mempool: mempool, - state: s, - ctx: &snow.Context{ - Log: logging.NoLog{}, - }, - } - manager := &manager{ - txExecutorBackend: &executor.Backend{ - Config: &config.Config{ - UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), - }, - Clk: &mockable.Clock{}, + exportedUTXO := &avax.UTXO{ + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: uint32(len(atomicTx.UTXOs())), }, - backend: backend, + Asset: exportedOutput.Asset, + Out: exportedOutput.Out, } + exportedUTXOID := exportedUTXO.InputID() + exportedUTXOBytes, err := txs.Codec.Marshal(txs.CodecVersion, exportedUTXO) + require.NoError(err) - onAccept := state.NewMockDiff(ctrl) - blkTx := txsmock.NewUnsignedTx(ctrl) - inputs := set.Of(ids.GenerateTestID()) - blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.AtomicTxExecutor{})).DoAndReturn( - func(e *executor.AtomicTxExecutor) error { - e.OnAccept = onAccept - e.Inputs = inputs - return nil - }, - ).Times(1) - - // We can't serialize [blkTx] because it isn't registered with blocks.Codec. - // Serialize this block with a dummy tx and replace it after creation with - // the mock tx. - // TODO allow serialization of mock txs. - apricotBlk, err := block.NewApricotAtomicBlock( - parentID, - 2, - &txs.Tx{ - Unsigned: &txs.AdvanceTimeTx{}, - Creds: []verify.Verifiable{}, + require.Equal( + &blockState{ + statelessBlock: atomicBlock, + + onAcceptState: onAccept, + + timestamp: initialTimestamp, + atomicRequests: map[ids.ID]*atomic.Requests{ + verifier.ctx.XChainID: { + PutRequests: []*atomic.Element{ + { + Key: exportedUTXOID[:], + Value: exportedUTXOBytes, + Traits: [][]byte{}, + }, + }, + }, + }, + verifiedHeights: set.Of(uint64(0)), }, + atomicBlockState, ) - require.NoError(err) - apricotBlk.Tx.Unsigned = blkTx - - // Set expectations for dependencies. - timestamp := time.Now() - parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) - parentStatelessBlk.EXPECT().Parent().Return(grandparentID).Times(1) - mempool.EXPECT().Remove([]*txs.Tx{apricotBlk.Tx}).Times(1) - onAccept.EXPECT().AddTx(apricotBlk.Tx, status.Committed).Times(1) - onAccept.EXPECT().GetTimestamp().Return(timestamp).Times(1) - - blk := manager.NewBlock(apricotBlk) - require.NoError(blk.Verify(context.Background())) - - require.Contains(manager.backend.blkIDToState, apricotBlk.ID()) - gotBlkState := manager.backend.blkIDToState[apricotBlk.ID()] - require.Equal(apricotBlk, gotBlkState.statelessBlock) - require.Equal(onAccept, gotBlkState.onAcceptState) - require.Equal(inputs, gotBlkState.inputs) - require.Equal(timestamp, gotBlkState.timestamp) - - // Visiting again should return nil without using dependencies. - require.NoError(blk.Verify(context.Background())) } func TestVerifierVisitStandardBlock(t *testing.T) { @@ -1222,13 +1150,12 @@ func TestVerifierVisitBanffAbortBlockUnexpectedParentState(t *testing.T) { } func TestBlockExecutionWithComplexity(t *testing.T) { - s := statetest.New(t, statetest.Config{}) - verifier := newTestVerifier(t, s) + verifier := newTestVerifier(t, testVerifierConfig{}) wallet := txstest.NewWallet( t, verifier.ctx, verifier.txExecutorBackend.Config, - s, + verifier.state, secp256k1fx.NewKeychain(genesis.EWOQKey), nil, // subnetIDs nil, // chainIDs @@ -1283,13 +1210,13 @@ func TestBlockExecutionWithComplexity(t *testing.T) { verifier.txExecutorBackend.Clk.Set(test.timestamp) timestamp, _, err := state.NextBlockTime( verifier.txExecutorBackend.Config.ValidatorFeeConfig, - s, + verifier.state, verifier.txExecutorBackend.Clk, ) require.NoError(err) - lastAcceptedID := s.GetLastAccepted() - lastAccepted, err := s.GetStatelessBlock(lastAcceptedID) + lastAcceptedID := verifier.state.GetLastAccepted() + lastAccepted, err := verifier.state.GetStatelessBlock(lastAcceptedID) require.NoError(err) blk, err := block.NewBanffStandardBlock( diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index 317068c97893..ed6fc57e0ffc 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -14,21 +14,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" ) -var _ txs.Visitor = (*AtomicTxExecutor)(nil) - -type AtomicTxExecutor struct { - // inputs, to be filled before visitor methods are called - backend *Backend - feeCalculator fee.Calculator - parentID ids.ID - stateVersions state.Versions - tx *txs.Tx - - // outputs of visitor execution - OnAccept state.Diff - Inputs set.Set[ids.ID] - atomicRequests map[ids.ID]*atomic.Requests -} +var _ txs.Visitor = (*atomicTxVisitor)(nil) // AtomicTx executes the atomic transaction [tx] and returns the resulting state // modifications. @@ -42,7 +28,7 @@ func AtomicTx( stateVersions state.Versions, tx *txs.Tx, ) (state.Diff, set.Set[ids.ID], map[ids.ID]*atomic.Requests, error) { - atomicExecutor := AtomicTxExecutor{ + atomicExecutor := atomicTxVisitor{ backend: backend, feeCalculator: feeCalculator, parentID: parentID, @@ -53,74 +39,88 @@ func AtomicTx( txID := tx.ID() return nil, nil, nil, fmt.Errorf("atomic tx %s failed execution: %w", txID, err) } - return atomicExecutor.OnAccept, atomicExecutor.Inputs, atomicExecutor.atomicRequests, nil + return atomicExecutor.onAccept, atomicExecutor.inputs, atomicExecutor.atomicRequests, nil +} + +type atomicTxVisitor struct { + // inputs, to be filled before visitor methods are called + backend *Backend + feeCalculator fee.Calculator + parentID ids.ID + stateVersions state.Versions + tx *txs.Tx + + // outputs of visitor execution + onAccept state.Diff + inputs set.Set[ids.ID] + atomicRequests map[ids.ID]*atomic.Requests } -func (*AtomicTxExecutor) AddValidatorTx(*txs.AddValidatorTx) error { +func (*atomicTxVisitor) AddValidatorTx(*txs.AddValidatorTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) AddSubnetValidatorTx(*txs.AddSubnetValidatorTx) error { +func (*atomicTxVisitor) AddSubnetValidatorTx(*txs.AddSubnetValidatorTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) AddDelegatorTx(*txs.AddDelegatorTx) error { +func (*atomicTxVisitor) AddDelegatorTx(*txs.AddDelegatorTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) CreateChainTx(*txs.CreateChainTx) error { +func (*atomicTxVisitor) CreateChainTx(*txs.CreateChainTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) CreateSubnetTx(*txs.CreateSubnetTx) error { +func (*atomicTxVisitor) CreateSubnetTx(*txs.CreateSubnetTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) AdvanceTimeTx(*txs.AdvanceTimeTx) error { +func (*atomicTxVisitor) AdvanceTimeTx(*txs.AdvanceTimeTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) RewardValidatorTx(*txs.RewardValidatorTx) error { +func (*atomicTxVisitor) RewardValidatorTx(*txs.RewardValidatorTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { +func (*atomicTxVisitor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) TransformSubnetTx(*txs.TransformSubnetTx) error { +func (*atomicTxVisitor) TransformSubnetTx(*txs.TransformSubnetTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { +func (*atomicTxVisitor) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { +func (*atomicTxVisitor) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { +func (*atomicTxVisitor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) BaseTx(*txs.BaseTx) error { +func (*atomicTxVisitor) BaseTx(*txs.BaseTx) error { return ErrWrongTxType } -func (*AtomicTxExecutor) ConvertSubnetTx(*txs.ConvertSubnetTx) error { +func (*atomicTxVisitor) ConvertSubnetTx(*txs.ConvertSubnetTx) error { return ErrWrongTxType } -func (e *AtomicTxExecutor) ImportTx(tx *txs.ImportTx) error { +func (e *atomicTxVisitor) ImportTx(tx *txs.ImportTx) error { return e.atomicTx(tx) } -func (e *AtomicTxExecutor) ExportTx(tx *txs.ExportTx) error { +func (e *atomicTxVisitor) ExportTx(tx *txs.ExportTx) error { return e.atomicTx(tx) } -func (e *AtomicTxExecutor) atomicTx(tx txs.UnsignedTx) error { +func (e *atomicTxVisitor) atomicTx(tx txs.UnsignedTx) error { onAccept, err := state.NewDiff( e.parentID, e.stateVersions, @@ -128,16 +128,16 @@ func (e *AtomicTxExecutor) atomicTx(tx txs.UnsignedTx) error { if err != nil { return err } - e.OnAccept = onAccept + e.onAccept = onAccept executor := StandardTxExecutor{ Backend: e.backend, - State: e.OnAccept, + State: e.onAccept, FeeCalculator: e.feeCalculator, Tx: e.tx, } err = tx.Visit(&executor) - e.Inputs = executor.Inputs + e.inputs = executor.Inputs e.atomicRequests = executor.AtomicRequests return err } From 5591626513c0de941774078a2c7c7f8f9f05a36f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 6 Nov 2024 13:25:34 -0500 Subject: [PATCH 310/400] nit --- .../txs/executor/atomic_tx_executor.go | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index ed6fc57e0ffc..90eb1725e057 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -14,7 +14,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" ) -var _ txs.Visitor = (*atomicTxVisitor)(nil) +var _ txs.Visitor = (*atomicTxExecutor)(nil) // AtomicTx executes the atomic transaction [tx] and returns the resulting state // modifications. @@ -28,7 +28,7 @@ func AtomicTx( stateVersions state.Versions, tx *txs.Tx, ) (state.Diff, set.Set[ids.ID], map[ids.ID]*atomic.Requests, error) { - atomicExecutor := atomicTxVisitor{ + atomicExecutor := atomicTxExecutor{ backend: backend, feeCalculator: feeCalculator, parentID: parentID, @@ -42,7 +42,7 @@ func AtomicTx( return atomicExecutor.onAccept, atomicExecutor.inputs, atomicExecutor.atomicRequests, nil } -type atomicTxVisitor struct { +type atomicTxExecutor struct { // inputs, to be filled before visitor methods are called backend *Backend feeCalculator fee.Calculator @@ -56,71 +56,71 @@ type atomicTxVisitor struct { atomicRequests map[ids.ID]*atomic.Requests } -func (*atomicTxVisitor) AddValidatorTx(*txs.AddValidatorTx) error { +func (*atomicTxExecutor) AddValidatorTx(*txs.AddValidatorTx) error { return ErrWrongTxType } -func (*atomicTxVisitor) AddSubnetValidatorTx(*txs.AddSubnetValidatorTx) error { +func (*atomicTxExecutor) AddSubnetValidatorTx(*txs.AddSubnetValidatorTx) error { return ErrWrongTxType } -func (*atomicTxVisitor) AddDelegatorTx(*txs.AddDelegatorTx) error { +func (*atomicTxExecutor) AddDelegatorTx(*txs.AddDelegatorTx) error { return ErrWrongTxType } -func (*atomicTxVisitor) CreateChainTx(*txs.CreateChainTx) error { +func (*atomicTxExecutor) CreateChainTx(*txs.CreateChainTx) error { return ErrWrongTxType } -func (*atomicTxVisitor) CreateSubnetTx(*txs.CreateSubnetTx) error { +func (*atomicTxExecutor) CreateSubnetTx(*txs.CreateSubnetTx) error { return ErrWrongTxType } -func (*atomicTxVisitor) AdvanceTimeTx(*txs.AdvanceTimeTx) error { +func (*atomicTxExecutor) AdvanceTimeTx(*txs.AdvanceTimeTx) error { return ErrWrongTxType } -func (*atomicTxVisitor) RewardValidatorTx(*txs.RewardValidatorTx) error { +func (*atomicTxExecutor) RewardValidatorTx(*txs.RewardValidatorTx) error { return ErrWrongTxType } -func (*atomicTxVisitor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { +func (*atomicTxExecutor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { return ErrWrongTxType } -func (*atomicTxVisitor) TransformSubnetTx(*txs.TransformSubnetTx) error { +func (*atomicTxExecutor) TransformSubnetTx(*txs.TransformSubnetTx) error { return ErrWrongTxType } -func (*atomicTxVisitor) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { +func (*atomicTxExecutor) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { return ErrWrongTxType } -func (*atomicTxVisitor) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { +func (*atomicTxExecutor) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { return ErrWrongTxType } -func (*atomicTxVisitor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { +func (*atomicTxExecutor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { return ErrWrongTxType } -func (*atomicTxVisitor) BaseTx(*txs.BaseTx) error { +func (*atomicTxExecutor) BaseTx(*txs.BaseTx) error { return ErrWrongTxType } -func (*atomicTxVisitor) ConvertSubnetTx(*txs.ConvertSubnetTx) error { +func (*atomicTxExecutor) ConvertSubnetTx(*txs.ConvertSubnetTx) error { return ErrWrongTxType } -func (e *atomicTxVisitor) ImportTx(tx *txs.ImportTx) error { +func (e *atomicTxExecutor) ImportTx(tx *txs.ImportTx) error { return e.atomicTx(tx) } -func (e *atomicTxVisitor) ExportTx(tx *txs.ExportTx) error { +func (e *atomicTxExecutor) ExportTx(tx *txs.ExportTx) error { return e.atomicTx(tx) } -func (e *atomicTxVisitor) atomicTx(tx txs.UnsignedTx) error { +func (e *atomicTxExecutor) atomicTx(tx txs.UnsignedTx) error { onAccept, err := state.NewDiff( e.parentID, e.stateVersions, From 0093c70cfe79ca93fd1f91bb8a5bbfa8b20ce9e9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 6 Nov 2024 15:35:17 -0500 Subject: [PATCH 311/400] Unexport ProposalTxExecutor --- vms/platformvm/block/executor/verifier.go | 17 +- .../block/executor/verifier_test.go | 123 ++++----- .../txs/executor/advance_time_test.go | 210 ++++++++------- .../txs/executor/proposal_tx_executor.go | 239 +++++++++-------- .../txs/executor/proposal_tx_executor_test.go | 240 ++++++++---------- .../txs/executor/reward_validator_test.go | 196 +++++++------- 6 files changed, 493 insertions(+), 532 deletions(-) diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index 24d2083d2ae3..dbff557ce263 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -394,15 +394,14 @@ func (v *verifier) proposalBlock( atomicRequests map[ids.ID]*atomic.Requests, onAcceptFunc func(), ) error { - txExecutor := executor.ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: v.txExecutorBackend, - FeeCalculator: feeCalculator, - Tx: tx, - } - - if err := tx.Unsigned.Visit(&txExecutor); err != nil { + err := executor.ProposalTx( + v.txExecutorBackend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) + if err != nil { txID := tx.ID() v.MarkDropped(txID, err) // cache tx as dropped return err diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 1a89a0bbda4c..7f7b21671620 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -108,89 +108,70 @@ func newTestVerifier(t testing.TB, c testVerifierConfig) *verifier { } func TestVerifierVisitProposalBlock(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - s := state.NewMockState(ctrl) - mempool := mempoolmock.NewMempool(ctrl) - parentID := ids.GenerateTestID() - parentStatelessBlk := block.NewMockBlock(ctrl) - parentOnAcceptState := state.NewMockDiff(ctrl) - timestamp := time.Now() - // One call for each of onCommitState and onAbortState. - parentOnAcceptState.EXPECT().GetTimestamp().Return(timestamp).Times(2) - parentOnAcceptState.EXPECT().GetFeeState().Return(gas.State{}).Times(2) - parentOnAcceptState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(2) - parentOnAcceptState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(2) - parentOnAcceptState.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(2) - - backend := &backend{ - lastAccepted: parentID, - blkIDToState: map[ids.ID]*blockState{ - parentID: { - statelessBlock: parentStatelessBlk, - onAcceptState: parentOnAcceptState, - }, - }, - Mempool: mempool, - state: s, - ctx: &snow.Context{ - Log: logging.NoLog{}, - }, - } - manager := &manager{ - txExecutorBackend: &executor.Backend{ - Config: &config.Config{ - UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), + var ( + require = require.New(t) + verifier = newTestVerifier(t, testVerifierConfig{ + Upgrades: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), + }) + initialTimestamp = verifier.state.GetTimestamp() + newTimestamp = initialTimestamp.Add(time.Second) + proposalTx = &txs.Tx{ + Unsigned: &txs.AdvanceTimeTx{ + Time: uint64(newTimestamp.Unix()), }, - Clk: &mockable.Clock{}, - }, - backend: backend, - } + } + ) + require.NoError(proposalTx.Initialize(txs.Codec)) - blkTx := txsmock.NewUnsignedTx(ctrl) - blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.ProposalTxExecutor{})).Return(nil).Times(1) + // Build the block that will be executed on top of the last accepted block. + lastAcceptedID := verifier.state.GetLastAccepted() + lastAccepted, err := verifier.state.GetStatelessBlock(lastAcceptedID) + require.NoError(err) - // We can't serialize [blkTx] because it isn't - // registered with the blocks.Codec. - // Serialize this block with a dummy tx - // and replace it after creation with the mock tx. - // TODO allow serialization of mock txs. - apricotBlk, err := block.NewApricotProposalBlock( - parentID, - 2, - &txs.Tx{ - Unsigned: &txs.AdvanceTimeTx{}, - Creds: []verify.Verifiable{}, - }, + proposalBlock, err := block.NewApricotProposalBlock( + lastAcceptedID, + lastAccepted.Height()+1, + proposalTx, ) require.NoError(err) - apricotBlk.Tx.Unsigned = blkTx - // Set expectations for dependencies. - tx := apricotBlk.Txs()[0] - parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) - mempool.EXPECT().Remove([]*txs.Tx{tx}).Times(1) + // Execute the block. + require.NoError(proposalBlock.Visit(verifier)) - // Visit the block - blk := manager.NewBlock(apricotBlk) - require.NoError(blk.Verify(context.Background())) - require.Contains(manager.backend.blkIDToState, apricotBlk.ID()) - gotBlkState := manager.backend.blkIDToState[apricotBlk.ID()] - require.Equal(apricotBlk, gotBlkState.statelessBlock) - require.Equal(timestamp, gotBlkState.timestamp) + // Verify that the block's execution was recorded as expected. + blkID := proposalBlock.ID() + require.Contains(verifier.blkIDToState, blkID) + executedBlockState := verifier.blkIDToState[blkID] - // Assert that the expected tx statuses are set. - _, gotStatus, err := gotBlkState.onCommitState.GetTx(tx.ID()) + txID := proposalTx.ID() + + onCommit := executedBlockState.onCommitState + require.NotNil(onCommit) + acceptedTx, acceptedStatus, err := onCommit.GetTx(txID) require.NoError(err) - require.Equal(status.Committed, gotStatus) + require.Equal(proposalTx, acceptedTx) + require.Equal(status.Committed, acceptedStatus) - _, gotStatus, err = gotBlkState.onAbortState.GetTx(tx.ID()) + onAbort := executedBlockState.onAbortState + require.NotNil(onAbort) + acceptedTx, acceptedStatus, err = onAbort.GetTx(txID) require.NoError(err) - require.Equal(status.Aborted, gotStatus) + require.Equal(proposalTx, acceptedTx) + require.Equal(status.Aborted, acceptedStatus) - // Visiting again should return nil without using dependencies. - require.NoError(blk.Verify(context.Background())) + require.Equal( + &blockState{ + proposalBlockState: proposalBlockState{ + onCommitState: onCommit, + onAbortState: onAbort, + }, + statelessBlock: proposalBlock, + + timestamp: initialTimestamp, + verifiedHeights: set.Of(uint64(0)), + }, + executedBlockState, + ) } func TestVerifierVisitAtomicBlock(t *testing.T) { diff --git a/vms/platformvm/txs/executor/advance_time_test.go b/vms/platformvm/txs/executor/advance_time_test.go index fa7d3583c68f..5acb4f29fca3 100644 --- a/vms/platformvm/txs/executor/advance_time_test.go +++ b/vms/platformvm/txs/executor/advance_time_test.go @@ -66,32 +66,31 @@ func TestAdvanceTimeTxUpdatePrimaryNetworkStakers(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&executor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) - validatorStaker, err := executor.OnCommitState.GetCurrentValidator(constants.PrimaryNetworkID, nodeID) + validatorStaker, err := onCommitState.GetCurrentValidator(constants.PrimaryNetworkID, nodeID) require.NoError(err) require.Equal(addPendingValidatorTx.ID(), validatorStaker.TxID) require.Equal(uint64(1370), validatorStaker.PotentialReward) // See rewards tests to explain why 1370 - _, err = executor.OnCommitState.GetPendingValidator(constants.PrimaryNetworkID, nodeID) + _, err = onCommitState.GetPendingValidator(constants.PrimaryNetworkID, nodeID) require.ErrorIs(err, database.ErrNotFound) - _, err = executor.OnAbortState.GetCurrentValidator(constants.PrimaryNetworkID, nodeID) + _, err = onAbortState.GetCurrentValidator(constants.PrimaryNetworkID, nodeID) require.ErrorIs(err, database.ErrNotFound) - validatorStaker, err = executor.OnAbortState.GetPendingValidator(constants.PrimaryNetworkID, nodeID) + validatorStaker, err = onAbortState.GetPendingValidator(constants.PrimaryNetworkID, nodeID) require.NoError(err) require.Equal(addPendingValidatorTx.ID(), validatorStaker.TxID) // Test VM validators - require.NoError(executor.OnCommitState.Apply(env.state)) + require.NoError(onCommitState.Apply(env.state)) env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) @@ -115,14 +114,13 @@ func TestAdvanceTimeTxTimestampTooEarly(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrChildBlockEarlierThanParent) } @@ -152,14 +150,13 @@ func TestAdvanceTimeTxTimestampTooLate(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrChildBlockAfterStakerChangeTime) } @@ -183,14 +180,13 @@ func TestAdvanceTimeTxTimestampTooLate(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrChildBlockAfterStakerChangeTime) } } @@ -428,16 +424,15 @@ func TestAdvanceTimeTxUpdateStakers(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&executor)) - - require.NoError(executor.OnCommitState.Apply(env.state)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) + + require.NoError(onCommitState.Apply(env.state)) } env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) @@ -562,20 +557,19 @@ func TestAdvanceTimeTxRemoveSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&executor)) - - _, err = executor.OnCommitState.GetCurrentValidator(subnetID, subnetValidatorNodeID) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) + + _, err = onCommitState.GetCurrentValidator(subnetID, subnetValidatorNodeID) require.ErrorIs(err, database.ErrNotFound) // Check VM Validators are removed successfully - require.NoError(executor.OnCommitState.Apply(env.state)) + require.NoError(onCommitState.Apply(env.state)) env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) @@ -644,16 +638,15 @@ func TestTrackedSubnet(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&executor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) - require.NoError(executor.OnCommitState.Apply(env.state)) + require.NoError(onCommitState.Apply(env.state)) env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) @@ -694,16 +687,15 @@ func TestAdvanceTimeTxDelegatorStakerWeight(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&executor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) - require.NoError(executor.OnCommitState.Apply(env.state)) + require.NoError(onCommitState.Apply(env.state)) env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) @@ -753,16 +745,15 @@ func TestAdvanceTimeTxDelegatorStakerWeight(t *testing.T) { onAbortState, err = state.NewDiff(lastAcceptedID, env) require.NoError(err) - executor = ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&executor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) - require.NoError(executor.OnCommitState.Apply(env.state)) + require.NoError(onCommitState.Apply(env.state)) env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) @@ -796,16 +787,15 @@ func TestAdvanceTimeTxDelegatorStakers(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&executor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) - require.NoError(executor.OnCommitState.Apply(env.state)) + require.NoError(onCommitState.Apply(env.state)) env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) @@ -854,16 +844,15 @@ func TestAdvanceTimeTxDelegatorStakers(t *testing.T) { onAbortState, err = state.NewDiff(lastAcceptedID, env) require.NoError(err) - executor = ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&executor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) - require.NoError(executor.OnCommitState.Apply(env.state)) + require.NoError(onCommitState.Apply(env.state)) env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) @@ -895,14 +884,13 @@ func TestAdvanceTimeTxAfterBanff(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrAdvanceTimeTxIssuedAfterBanff) } diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go index 4c4c1f5c2b91..5fe0851e92be 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor.go @@ -30,7 +30,7 @@ const ( ) var ( - _ txs.Visitor = (*ProposalTxExecutor)(nil) + _ txs.Visitor = (*proposalTxExecutor)(nil) ErrRemoveStakerTooEarly = errors.New("attempting to remove staker before their end time") ErrRemoveWrongStaker = errors.New("attempting to remove wrong staker") @@ -42,259 +42,280 @@ var ( ErrAdvanceTimeTxIssuedAfterBanff = errors.New("AdvanceTimeTx issued after Banff") ) -type ProposalTxExecutor struct { +func ProposalTx( + backend *Backend, + feeCalculator fee.Calculator, + tx *txs.Tx, + onCommitState state.Diff, + onAbortState state.Diff, +) error { + proposalExecutor := proposalTxExecutor{ + backend: backend, + feeCalculator: feeCalculator, + tx: tx, + onCommitState: onCommitState, + onAbortState: onAbortState, + } + if err := tx.Unsigned.Visit(&proposalExecutor); err != nil { + txID := tx.ID() + return fmt.Errorf("proposal tx %s failed execution: %w", txID, err) + } + return nil +} + +type proposalTxExecutor struct { // inputs, to be filled before visitor methods are called - *Backend - FeeCalculator fee.Calculator - Tx *txs.Tx - // [OnCommitState] is the state used for validation. - // [OnCommitState] is modified by this struct's methods to + backend *Backend + feeCalculator fee.Calculator + tx *txs.Tx + // [onCommitState] is the state used for validation. + // [onCommitState] is modified by this struct's methods to // reflect changes made to the state if the proposal is committed. // - // Invariant: Both [OnCommitState] and [OnAbortState] represent the same + // Invariant: Both [onCommitState] and [OnAbortState] represent the same // state when provided to this struct. - OnCommitState state.Diff - // [OnAbortState] is modified by this struct's methods to + onCommitState state.Diff + // [onAbortState] is modified by this struct's methods to // reflect changes made to the state if the proposal is aborted. - OnAbortState state.Diff + onAbortState state.Diff } -func (*ProposalTxExecutor) CreateChainTx(*txs.CreateChainTx) error { +func (*proposalTxExecutor) CreateChainTx(*txs.CreateChainTx) error { return ErrWrongTxType } -func (*ProposalTxExecutor) CreateSubnetTx(*txs.CreateSubnetTx) error { +func (*proposalTxExecutor) CreateSubnetTx(*txs.CreateSubnetTx) error { return ErrWrongTxType } -func (*ProposalTxExecutor) ImportTx(*txs.ImportTx) error { +func (*proposalTxExecutor) ImportTx(*txs.ImportTx) error { return ErrWrongTxType } -func (*ProposalTxExecutor) ExportTx(*txs.ExportTx) error { +func (*proposalTxExecutor) ExportTx(*txs.ExportTx) error { return ErrWrongTxType } -func (*ProposalTxExecutor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { +func (*proposalTxExecutor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { return ErrWrongTxType } -func (*ProposalTxExecutor) TransformSubnetTx(*txs.TransformSubnetTx) error { +func (*proposalTxExecutor) TransformSubnetTx(*txs.TransformSubnetTx) error { return ErrWrongTxType } -func (*ProposalTxExecutor) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { +func (*proposalTxExecutor) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { return ErrWrongTxType } -func (*ProposalTxExecutor) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { +func (*proposalTxExecutor) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { return ErrWrongTxType } -func (*ProposalTxExecutor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { +func (*proposalTxExecutor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { return ErrWrongTxType } -func (*ProposalTxExecutor) BaseTx(*txs.BaseTx) error { +func (*proposalTxExecutor) BaseTx(*txs.BaseTx) error { return ErrWrongTxType } -func (*ProposalTxExecutor) ConvertSubnetTx(*txs.ConvertSubnetTx) error { +func (*proposalTxExecutor) ConvertSubnetTx(*txs.ConvertSubnetTx) error { return ErrWrongTxType } -func (e *ProposalTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { +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 // StandardBlocks. - currentTimestamp := e.OnCommitState.GetTimestamp() - if e.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) { + currentTimestamp := e.onCommitState.GetTimestamp() + if e.backend.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) { return fmt.Errorf( "%w: timestamp (%s) >= Banff fork time (%s)", ErrProposedAddStakerTxAfterBanff, currentTimestamp, - e.Config.UpgradeConfig.BanffTime, + e.backend.Config.UpgradeConfig.BanffTime, ) } onAbortOuts, err := verifyAddValidatorTx( - e.Backend, - e.FeeCalculator, - e.OnCommitState, - e.Tx, + e.backend, + e.feeCalculator, + e.onCommitState, + e.tx, tx, ) if err != nil { return err } - txID := e.Tx.ID() + txID := e.tx.ID() // Set up the state if this tx is committed // Consume the UTXOs - avax.Consume(e.OnCommitState, tx.Ins) + avax.Consume(e.onCommitState, tx.Ins) // Produce the UTXOs - avax.Produce(e.OnCommitState, txID, tx.Outs) + avax.Produce(e.onCommitState, txID, tx.Outs) newStaker, err := state.NewPendingStaker(txID, tx) if err != nil { return err } - if err := e.OnCommitState.PutPendingValidator(newStaker); err != nil { + if err := e.onCommitState.PutPendingValidator(newStaker); err != nil { return err } // Set up the state if this tx is aborted // Consume the UTXOs - avax.Consume(e.OnAbortState, tx.Ins) + avax.Consume(e.onAbortState, tx.Ins) // Produce the UTXOs - avax.Produce(e.OnAbortState, txID, onAbortOuts) + avax.Produce(e.onAbortState, txID, onAbortOuts) return nil } -func (e *ProposalTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { +func (e *proposalTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { // AddSubnetValidatorTx is a proposal transaction until the Banff fork // activation. Following the activation, AddSubnetValidatorTxs must be // issued into StandardBlocks. - currentTimestamp := e.OnCommitState.GetTimestamp() - if e.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) { + currentTimestamp := e.onCommitState.GetTimestamp() + if e.backend.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) { return fmt.Errorf( "%w: timestamp (%s) >= Banff fork time (%s)", ErrProposedAddStakerTxAfterBanff, currentTimestamp, - e.Config.UpgradeConfig.BanffTime, + e.backend.Config.UpgradeConfig.BanffTime, ) } if err := verifyAddSubnetValidatorTx( - e.Backend, - e.FeeCalculator, - e.OnCommitState, - e.Tx, + e.backend, + e.feeCalculator, + e.onCommitState, + e.tx, tx, ); err != nil { return err } - txID := e.Tx.ID() + txID := e.tx.ID() // Set up the state if this tx is committed // Consume the UTXOs - avax.Consume(e.OnCommitState, tx.Ins) + avax.Consume(e.onCommitState, tx.Ins) // Produce the UTXOs - avax.Produce(e.OnCommitState, txID, tx.Outs) + avax.Produce(e.onCommitState, txID, tx.Outs) newStaker, err := state.NewPendingStaker(txID, tx) if err != nil { return err } - if err := e.OnCommitState.PutPendingValidator(newStaker); err != nil { + if err := e.onCommitState.PutPendingValidator(newStaker); err != nil { return err } // Set up the state if this tx is aborted // Consume the UTXOs - avax.Consume(e.OnAbortState, tx.Ins) + avax.Consume(e.onAbortState, tx.Ins) // Produce the UTXOs - avax.Produce(e.OnAbortState, txID, tx.Outs) + avax.Produce(e.onAbortState, txID, tx.Outs) return nil } -func (e *ProposalTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { +func (e *proposalTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { // AddDelegatorTx is a proposal transaction until the Banff fork // activation. Following the activation, AddDelegatorTxs must be issued into // StandardBlocks. - currentTimestamp := e.OnCommitState.GetTimestamp() - if e.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) { + currentTimestamp := e.onCommitState.GetTimestamp() + if e.backend.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) { return fmt.Errorf( "%w: timestamp (%s) >= Banff fork time (%s)", ErrProposedAddStakerTxAfterBanff, currentTimestamp, - e.Config.UpgradeConfig.BanffTime, + e.backend.Config.UpgradeConfig.BanffTime, ) } onAbortOuts, err := verifyAddDelegatorTx( - e.Backend, - e.FeeCalculator, - e.OnCommitState, - e.Tx, + e.backend, + e.feeCalculator, + e.onCommitState, + e.tx, tx, ) if err != nil { return err } - txID := e.Tx.ID() + txID := e.tx.ID() // Set up the state if this tx is committed // Consume the UTXOs - avax.Consume(e.OnCommitState, tx.Ins) + avax.Consume(e.onCommitState, tx.Ins) // Produce the UTXOs - avax.Produce(e.OnCommitState, txID, tx.Outs) + avax.Produce(e.onCommitState, txID, tx.Outs) newStaker, err := state.NewPendingStaker(txID, tx) if err != nil { return err } - e.OnCommitState.PutPendingDelegator(newStaker) + e.onCommitState.PutPendingDelegator(newStaker) // Set up the state if this tx is aborted // Consume the UTXOs - avax.Consume(e.OnAbortState, tx.Ins) + avax.Consume(e.onAbortState, tx.Ins) // Produce the UTXOs - avax.Produce(e.OnAbortState, txID, onAbortOuts) + avax.Produce(e.onAbortState, txID, onAbortOuts) return nil } -func (e *ProposalTxExecutor) AdvanceTimeTx(tx *txs.AdvanceTimeTx) error { +func (e *proposalTxExecutor) AdvanceTimeTx(tx *txs.AdvanceTimeTx) error { switch { case tx == nil: return txs.ErrNilTx - case len(e.Tx.Creds) != 0: + case len(e.tx.Creds) != 0: return errWrongNumberOfCredentials } // Validate [newChainTime] newChainTime := tx.Timestamp() - if e.Config.UpgradeConfig.IsBanffActivated(newChainTime) { + if e.backend.Config.UpgradeConfig.IsBanffActivated(newChainTime) { return fmt.Errorf( "%w: proposed timestamp (%s) >= Banff fork time (%s)", ErrAdvanceTimeTxIssuedAfterBanff, newChainTime, - e.Config.UpgradeConfig.BanffTime, + e.backend.Config.UpgradeConfig.BanffTime, ) } - now := e.Clk.Time() + now := e.backend.Clk.Time() if err := VerifyNewChainTime( - e.Config.ValidatorFeeConfig, + e.backend.Config.ValidatorFeeConfig, newChainTime, now, - e.OnCommitState, + 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 } -func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error { +func (e *proposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error { switch { case tx == nil: return txs.ErrNilTx case tx.TxID == ids.Empty: return ErrInvalidID - case len(e.Tx.Creds) != 0: + case len(e.tx.Creds) != 0: return errWrongNumberOfCredentials } - currentStakerIterator, err := e.OnCommitState.GetCurrentStakerIterator() + currentStakerIterator, err := e.onCommitState.GetCurrentStakerIterator() if err != nil { return err } @@ -314,7 +335,7 @@ func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error } // Verify that the chain's timestamp is the validator's end time - currentChainTime := e.OnCommitState.GetTimestamp() + currentChainTime := e.onCommitState.GetTimestamp() if !stakerToReward.EndTime.Equal(currentChainTime) { return fmt.Errorf( "%w: TxID = %s with %s < %s", @@ -325,7 +346,7 @@ func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error ) } - stakerTx, _, err := e.OnCommitState.GetTx(stakerToReward.TxID) + stakerTx, _, err := e.onCommitState.GetTx(stakerToReward.TxID) if err != nil { return fmt.Errorf("failed to get next removed staker tx: %w", err) } @@ -339,16 +360,16 @@ func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error } // Handle staker lifecycle. - e.OnCommitState.DeleteCurrentValidator(stakerToReward) - e.OnAbortState.DeleteCurrentValidator(stakerToReward) + e.onCommitState.DeleteCurrentValidator(stakerToReward) + e.onAbortState.DeleteCurrentValidator(stakerToReward) case txs.DelegatorTx: if err := e.rewardDelegatorTx(uStakerTx, stakerToReward); err != nil { return err } // Handle staker lifecycle. - e.OnCommitState.DeleteCurrentDelegator(stakerToReward) - e.OnAbortState.DeleteCurrentDelegator(stakerToReward) + e.onCommitState.DeleteCurrentDelegator(stakerToReward) + e.onAbortState.DeleteCurrentDelegator(stakerToReward) default: // Invariant: Permissioned stakers are removed by the advancement of // time and the current chain timestamp is == this staker's @@ -358,7 +379,7 @@ func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error } // If the reward is aborted, then the current supply should be decreased. - currentSupply, err := e.OnAbortState.GetCurrentSupply(stakerToReward.SubnetID) + currentSupply, err := e.onAbortState.GetCurrentSupply(stakerToReward.SubnetID) if err != nil { return err } @@ -366,11 +387,11 @@ func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error if err != nil { return err } - e.OnAbortState.SetCurrentSupply(stakerToReward.SubnetID, newSupply) + e.onAbortState.SetCurrentSupply(stakerToReward.SubnetID, newSupply) return nil } -func (e *ProposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, validator *state.Staker) error { +func (e *proposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, validator *state.Staker) error { var ( txID = validator.TxID stake = uValidatorTx.Stake() @@ -390,8 +411,8 @@ func (e *ProposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, val Asset: out.Asset, Out: out.Output(), } - e.OnCommitState.AddUTXO(utxo) - e.OnAbortState.AddUTXO(utxo) + e.onCommitState.AddUTXO(utxo) + e.onAbortState.AddUTXO(utxo) } utxosOffset := 0 @@ -400,7 +421,7 @@ func (e *ProposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, val reward := validator.PotentialReward if reward > 0 { validationRewardsOwner := uValidatorTx.ValidationRewardsOwner() - outIntf, err := e.Fx.CreateOutput(reward, validationRewardsOwner) + outIntf, err := e.backend.Fx.CreateOutput(reward, validationRewardsOwner) if err != nil { return fmt.Errorf("failed to create output: %w", err) } @@ -417,14 +438,14 @@ func (e *ProposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, val Asset: stakeAsset, Out: out, } - e.OnCommitState.AddUTXO(utxo) - e.OnCommitState.AddRewardUTXO(txID, utxo) + e.onCommitState.AddUTXO(utxo) + e.onCommitState.AddRewardUTXO(txID, utxo) utxosOffset++ } // Provide the accrued delegatee rewards from successful delegations here. - delegateeReward, err := e.OnCommitState.GetDelegateeReward( + delegateeReward, err := e.onCommitState.GetDelegateeReward( validator.SubnetID, validator.NodeID, ) @@ -437,7 +458,7 @@ func (e *ProposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, val } delegationRewardsOwner := uValidatorTx.DelegationRewardsOwner() - outIntf, err := e.Fx.CreateOutput(delegateeReward, delegationRewardsOwner) + outIntf, err := e.backend.Fx.CreateOutput(delegateeReward, delegationRewardsOwner) if err != nil { return fmt.Errorf("failed to create output: %w", err) } @@ -454,8 +475,8 @@ func (e *ProposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, val Asset: stakeAsset, Out: out, } - e.OnCommitState.AddUTXO(onCommitUtxo) - e.OnCommitState.AddRewardUTXO(txID, onCommitUtxo) + e.onCommitState.AddUTXO(onCommitUtxo) + e.onCommitState.AddRewardUTXO(txID, onCommitUtxo) // Note: There is no [offset] if the RewardValidatorTx is // aborted, because the validator reward is not awarded. @@ -467,12 +488,12 @@ func (e *ProposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, val Asset: stakeAsset, Out: out, } - e.OnAbortState.AddUTXO(onAbortUtxo) - e.OnAbortState.AddRewardUTXO(txID, onAbortUtxo) + e.onAbortState.AddUTXO(onAbortUtxo) + e.onAbortState.AddRewardUTXO(txID, onAbortUtxo) return nil } -func (e *ProposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, delegator *state.Staker) error { +func (e *proposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, delegator *state.Staker) error { var ( txID = delegator.TxID stake = uDelegatorTx.Stake() @@ -492,18 +513,18 @@ func (e *ProposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, del Asset: out.Asset, Out: out.Output(), } - e.OnCommitState.AddUTXO(utxo) - e.OnAbortState.AddUTXO(utxo) + e.onCommitState.AddUTXO(utxo) + e.onAbortState.AddUTXO(utxo) } // We're (possibly) rewarding a delegator, so we need to fetch // the validator they are delegated to. - validator, err := e.OnCommitState.GetCurrentValidator(delegator.SubnetID, delegator.NodeID) + validator, err := e.onCommitState.GetCurrentValidator(delegator.SubnetID, delegator.NodeID) if err != nil { return fmt.Errorf("failed to get whether %s is a validator: %w", delegator.NodeID, err) } - vdrTxIntf, _, err := e.OnCommitState.GetTx(validator.TxID) + vdrTxIntf, _, err := e.onCommitState.GetTx(validator.TxID) if err != nil { return fmt.Errorf("failed to get whether %s is a validator: %w", delegator.NodeID, err) } @@ -526,7 +547,7 @@ func (e *ProposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, del reward := delegatorReward if reward > 0 { rewardsOwner := uDelegatorTx.RewardsOwner() - outIntf, err := e.Fx.CreateOutput(reward, rewardsOwner) + outIntf, err := e.backend.Fx.CreateOutput(reward, rewardsOwner) if err != nil { return fmt.Errorf("failed to create output: %w", err) } @@ -543,8 +564,8 @@ func (e *ProposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, del Out: out, } - e.OnCommitState.AddUTXO(utxo) - e.OnCommitState.AddRewardUTXO(txID, utxo) + e.onCommitState.AddUTXO(utxo) + e.onCommitState.AddRewardUTXO(txID, utxo) utxosOffset++ } @@ -554,8 +575,8 @@ func (e *ProposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, del } // Reward the delegatee here - if e.Config.UpgradeConfig.IsCortinaActivated(validator.StartTime) { - previousDelegateeReward, err := e.OnCommitState.GetDelegateeReward( + if e.backend.Config.UpgradeConfig.IsCortinaActivated(validator.StartTime) { + previousDelegateeReward, err := e.onCommitState.GetDelegateeReward( validator.SubnetID, validator.NodeID, ) @@ -570,7 +591,7 @@ func (e *ProposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, del // For any validators starting after [CortinaTime], we defer rewarding the // [reward] until their staking period is over. - err = e.OnCommitState.SetDelegateeReward( + err = e.onCommitState.SetDelegateeReward( validator.SubnetID, validator.NodeID, newDelegateeReward, @@ -582,7 +603,7 @@ func (e *ProposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, del // For any validators who started prior to [CortinaTime], we issue the // [delegateeReward] immediately. delegationRewardsOwner := vdrTx.DelegationRewardsOwner() - outIntf, err := e.Fx.CreateOutput(delegateeReward, delegationRewardsOwner) + outIntf, err := e.backend.Fx.CreateOutput(delegateeReward, delegationRewardsOwner) if err != nil { return fmt.Errorf("failed to create output: %w", err) } @@ -599,8 +620,8 @@ func (e *ProposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, del Out: out, } - e.OnCommitState.AddUTXO(utxo) - e.OnCommitState.AddRewardUTXO(txID, utxo) + e.onCommitState.AddUTXO(utxo) + e.onCommitState.AddRewardUTXO(txID, utxo) } return nil } diff --git a/vms/platformvm/txs/executor/proposal_tx_executor_test.go b/vms/platformvm/txs/executor/proposal_tx_executor_test.go index eed2e6a6ddeb..d6759ee54767 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor_test.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor_test.go @@ -268,14 +268,13 @@ func TestProposalTxExecuteAddDelegator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, tt.expectedErr) }) } @@ -316,14 +315,13 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrPeriodMismatch) } @@ -355,14 +353,13 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&executor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) } // Add a validator to pending validator set of primary network @@ -414,14 +411,13 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrNotValidator) } @@ -468,14 +464,13 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrPeriodMismatch) } @@ -505,14 +500,13 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrPeriodMismatch) } @@ -542,14 +536,13 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&executor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) } // Case: Proposed validator start validating at/before current timestamp @@ -581,14 +574,13 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrTimestampNotBeforeStartTime) } @@ -652,14 +644,13 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: duplicateSubnetTx, - } - err = duplicateSubnetTx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + duplicateSubnetTx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrDuplicateValidator) } @@ -699,14 +690,13 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, errUnauthorizedSubnetModification) } @@ -740,14 +730,13 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, errUnauthorizedSubnetModification) } @@ -791,14 +780,13 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrDuplicateValidator) } } @@ -838,14 +826,13 @@ func TestProposalTxExecuteAddValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrTimestampNotBeforeStartTime) } @@ -873,14 +860,13 @@ func TestProposalTxExecuteAddValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrAlreadyValidator) } @@ -922,14 +908,13 @@ func TestProposalTxExecuteAddValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrAlreadyValidator) } @@ -965,14 +950,13 @@ func TestProposalTxExecuteAddValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - executor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrFlowCheckFailed) } } diff --git a/vms/platformvm/txs/executor/reward_validator_test.go b/vms/platformvm/txs/executor/reward_validator_test.go index 86fb83f994ed..e10da2bfd20e 100644 --- a/vms/platformvm/txs/executor/reward_validator_test.go +++ b/vms/platformvm/txs/executor/reward_validator_test.go @@ -61,14 +61,13 @@ func TestRewardValidatorTxExecuteOnCommit(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAbortState) - txExecutor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&txExecutor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrRemoveStakerTooEarly) // Advance chain timestamp to time that next validator leaves @@ -84,14 +83,13 @@ func TestRewardValidatorTxExecuteOnCommit(t *testing.T) { onAbortState, err = state.NewDiff(lastAcceptedID, env) require.NoError(err) - txExecutor = ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&txExecutor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrRemoveWrongStaker) // Case 3: Happy path @@ -104,16 +102,15 @@ func TestRewardValidatorTxExecuteOnCommit(t *testing.T) { onAbortState, err = state.NewDiff(lastAcceptedID, env) require.NoError(err) - txExecutor = ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&txExecutor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) - onCommitStakerIterator, err := txExecutor.OnCommitState.GetCurrentStakerIterator() + onCommitStakerIterator, err := onCommitState.GetCurrentStakerIterator() require.NoError(err) require.True(onCommitStakerIterator.Next()) @@ -128,7 +125,7 @@ func TestRewardValidatorTxExecuteOnCommit(t *testing.T) { oldBalance, err := avax.GetBalance(env.state, stakeOwners) require.NoError(err) - require.NoError(txExecutor.OnCommitState.Apply(env.state)) + require.NoError(onCommitState.Apply(env.state)) env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) @@ -165,14 +162,13 @@ func TestRewardValidatorTxExecuteOnAbort(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAbortState) - txExecutor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&txExecutor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrRemoveStakerTooEarly) // Advance chain timestamp to time that next validator leaves @@ -182,14 +178,13 @@ func TestRewardValidatorTxExecuteOnAbort(t *testing.T) { tx, err = newRewardValidatorTx(t, ids.GenerateTestID()) require.NoError(err) - txExecutor = ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&txExecutor) + err = ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + ) require.ErrorIs(err, ErrRemoveWrongStaker) // Case 3: Happy path @@ -202,16 +197,15 @@ func TestRewardValidatorTxExecuteOnAbort(t *testing.T) { onAbortState, err = state.NewDiff(lastAcceptedID, env) require.NoError(err) - txExecutor = ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&txExecutor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) - onAbortStakerIterator, err := txExecutor.OnAbortState.GetCurrentStakerIterator() + onAbortStakerIterator, err := onAbortState.GetCurrentStakerIterator() require.NoError(err) require.True(onAbortStakerIterator.Next()) @@ -226,7 +220,7 @@ func TestRewardValidatorTxExecuteOnAbort(t *testing.T) { oldBalance, err := avax.GetBalance(env.state, stakeOwners) require.NoError(err) - require.NoError(txExecutor.OnAbortState.Apply(env.state)) + require.NoError(onAbortState.Apply(env.state)) env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) @@ -322,14 +316,13 @@ func TestRewardDelegatorTxExecuteOnCommitPreDelegateeDeferral(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - txExecutor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&txExecutor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) vdrDestSet := set.Of(vdrRewardAddress) delDestSet := set.Of(delRewardAddress) @@ -341,7 +334,7 @@ func TestRewardDelegatorTxExecuteOnCommitPreDelegateeDeferral(t *testing.T) { oldDelBalance, err := avax.GetBalance(env.state, delDestSet) require.NoError(err) - require.NoError(txExecutor.OnCommitState.Apply(env.state)) + require.NoError(onCommitState.Apply(env.state)) env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) @@ -464,14 +457,13 @@ func TestRewardDelegatorTxExecuteOnCommitPostDelegateeDeferral(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - txExecutor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&txExecutor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) // The delegator should be rewarded if the ProposalTx is committed. Since the // delegatee's share is 25%, we expect the delegator to receive 75% of the reward. @@ -498,7 +490,7 @@ func TestRewardDelegatorTxExecuteOnCommitPostDelegateeDeferral(t *testing.T) { require.ErrorIs(err, database.ErrNotFound) // Commit Delegator Diff - require.NoError(txExecutor.OnCommitState.Apply(env.state)) + require.NoError(onCommitState.Apply(env.state)) env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) @@ -513,14 +505,13 @@ func TestRewardDelegatorTxExecuteOnCommitPostDelegateeDeferral(t *testing.T) { onAbortState, err = state.NewDiff(lastAcceptedID, env) require.NoError(err) - txExecutor = ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&txExecutor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) require.NotEqual(vdrStaker.TxID, delStaker.TxID) @@ -569,7 +560,7 @@ func TestRewardDelegatorTxExecuteOnCommitPostDelegateeDeferral(t *testing.T) { require.ErrorIs(err, database.ErrNotFound) // Commit Validator Diff - require.NoError(txExecutor.OnCommitState.Apply(env.state)) + require.NoError(onCommitState.Apply(env.state)) env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) @@ -687,14 +678,13 @@ func TestRewardDelegatorTxAndValidatorTxExecuteOnCommitPostDelegateeDeferral(t * require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, delOnCommitState) - txExecutor := ProposalTxExecutor{ - OnCommitState: delOnCommitState, - OnAbortState: delOnAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&txExecutor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + delOnCommitState, + delOnAbortState, + )) // Create Validator Diffs testID := ids.GenerateTestID() @@ -709,14 +699,13 @@ func TestRewardDelegatorTxAndValidatorTxExecuteOnCommitPostDelegateeDeferral(t * tx, err = newRewardValidatorTx(t, vdrTx.ID()) require.NoError(err) - txExecutor = ProposalTxExecutor{ - OnCommitState: vdrOnCommitState, - OnAbortState: vdrOnAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&txExecutor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + vdrOnCommitState, + vdrOnAbortState, + )) // aborted validator tx should still distribute accrued delegator rewards numVdrStakeUTXOs := uint32(len(delTx.Unsigned.InputIDs())) @@ -849,14 +838,13 @@ func TestRewardDelegatorTxExecuteOnAbort(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onCommitState) - txExecutor := ProposalTxExecutor{ - OnCommitState: onCommitState, - OnAbortState: onAbortState, - Backend: &env.backend, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&txExecutor)) + require.NoError(ProposalTx( + &env.backend, + feeCalculator, + tx, + onCommitState, + onAbortState, + )) vdrDestSet := set.Of(vdrRewardAddress) delDestSet := set.Of(delRewardAddress) @@ -868,7 +856,7 @@ func TestRewardDelegatorTxExecuteOnAbort(t *testing.T) { oldDelBalance, err := avax.GetBalance(env.state, delDestSet) require.NoError(err) - require.NoError(txExecutor.OnAbortState.Apply(env.state)) + require.NoError(onAbortState.Apply(env.state)) env.state.SetHeight(dummyHeight) require.NoError(env.state.Commit()) From a0e6bcd3115b05fee3cc011052b9aebd5f049ebb Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 6 Nov 2024 16:29:22 -0500 Subject: [PATCH 312/400] comment new function --- .../txs/executor/proposal_tx_executor.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go index 5fe0851e92be..061f3ee60e95 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor.go @@ -42,6 +42,17 @@ var ( ErrAdvanceTimeTxIssuedAfterBanff = errors.New("AdvanceTimeTx issued after Banff") ) +// ProposalTx executes the proposal transaction [tx] and modifies +// [onCommitState] and [onAbortState] according to the transaction logic. +// +// [onCommitState] will be modified to reflect the changes made to the state if +// the proposal is committed. +// +// [onAbortState] will be modified to reflect the changes made to the state if +// the proposal is aborted. +// +// Invariant: It is assumed that [onCommitState] and [onAbortState] represent +// the same state when passed into this function. func ProposalTx( backend *Backend, feeCalculator fee.Calculator, @@ -71,9 +82,6 @@ type proposalTxExecutor struct { // [onCommitState] is the state used for validation. // [onCommitState] is modified by this struct's methods to // reflect changes made to the state if the proposal is committed. - // - // Invariant: Both [onCommitState] and [OnAbortState] represent the same - // state when provided to this struct. onCommitState state.Diff // [onAbortState] is modified by this struct's methods to // reflect changes made to the state if the proposal is aborted. From 5e46925808518f5c0fe503a66fe954c55fc5c177 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 6 Nov 2024 16:29:43 -0500 Subject: [PATCH 313/400] nit --- vms/platformvm/txs/executor/proposal_tx_executor.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go index 061f3ee60e95..03b03c889a23 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor.go @@ -42,8 +42,7 @@ var ( ErrAdvanceTimeTxIssuedAfterBanff = errors.New("AdvanceTimeTx issued after Banff") ) -// ProposalTx executes the proposal transaction [tx] and modifies -// [onCommitState] and [onAbortState] according to the transaction logic. +// ProposalTx executes the proposal transaction [tx]. // // [onCommitState] will be modified to reflect the changes made to the state if // the proposal is committed. From 706c83301f89853e2e269f0fc0318e63af2d5b1b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 11:03:27 -0500 Subject: [PATCH 314/400] Standardize standard tx executor --- vms/platformvm/block/builder/builder.go | 20 +- vms/platformvm/block/builder/helpers_test.go | 14 +- vms/platformvm/block/executor/helpers_test.go | 14 +- vms/platformvm/block/executor/manager.go | 13 +- vms/platformvm/block/executor/verifier.go | 24 +- .../block/executor/verifier_test.go | 323 +++++----- .../txs/executor/atomic_tx_executor.go | 27 +- .../txs/executor/create_chain_test.go | 79 ++- .../txs/executor/create_subnet_test.go | 13 +- vms/platformvm/txs/executor/export_test.go | 14 +- vms/platformvm/txs/executor/helpers_test.go | 14 +- vms/platformvm/txs/executor/import_test.go | 14 +- .../txs/executor/standard_tx_executor.go | 405 ++++++------ .../txs/executor/standard_tx_executor_test.go | 590 +++++++++--------- 14 files changed, 754 insertions(+), 810 deletions(-) diff --git a/vms/platformvm/block/builder/builder.go b/vms/platformvm/block/builder/builder.go index 3c88e8277929..b57cc9ecb4e5 100644 --- a/vms/platformvm/block/builder/builder.go +++ b/vms/platformvm/block/builder/builder.go @@ -516,32 +516,30 @@ func executeTx( return false, err } - executor := &txexecutor.StandardTxExecutor{ - Backend: backend, - State: txDiff, - FeeCalculator: feeCalculator, - Tx: tx, - } - - err = tx.Unsigned.Visit(executor) + txInputs, _, _, err := txexecutor.StandardTx( + backend, + feeCalculator, + tx, + txDiff, + ) if err != nil { txID := tx.ID() mempool.MarkDropped(txID, err) return false, nil } - if inputs.Overlaps(executor.Inputs) { + if inputs.Overlaps(txInputs) { txID := tx.ID() mempool.MarkDropped(txID, blockexecutor.ErrConflictingBlockTxs) return false, nil } - err = manager.VerifyUniqueInputs(parentID, executor.Inputs) + err = manager.VerifyUniqueInputs(parentID, txInputs) if err != nil { txID := tx.ID() mempool.MarkDropped(txID, err) return false, nil } - inputs.Union(executor.Inputs) + inputs.Union(txInputs) txDiff.AddTx(tx, status.Committed) return true, txDiff.Apply(stateDiff) diff --git a/vms/platformvm/block/builder/helpers_test.go b/vms/platformvm/block/builder/helpers_test.go index faba1b1ed695..c779609baf54 100644 --- a/vms/platformvm/block/builder/helpers_test.go +++ b/vms/platformvm/block/builder/helpers_test.go @@ -247,13 +247,13 @@ func addSubnet(t *testing.T, env *environment) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, stateDiff) - executor := txexecutor.StandardTxExecutor{ - Backend: &env.backend, - State: stateDiff, - FeeCalculator: feeCalculator, - Tx: testSubnet1, - } - require.NoError(testSubnet1.Unsigned.Visit(&executor)) + _, _, _, err = txexecutor.StandardTx( + &env.backend, + feeCalculator, + testSubnet1, + stateDiff, + ) + require.NoError(err) stateDiff.AddTx(testSubnet1, status.Committed) require.NoError(stateDiff.Apply(env.state)) diff --git a/vms/platformvm/block/executor/helpers_test.go b/vms/platformvm/block/executor/helpers_test.go index 4e3da112739f..d0616a8e374c 100644 --- a/vms/platformvm/block/executor/helpers_test.go +++ b/vms/platformvm/block/executor/helpers_test.go @@ -253,13 +253,13 @@ func addSubnet(t testing.TB, env *environment) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, stateDiff) - executor := executor.StandardTxExecutor{ - Backend: env.backend, - State: stateDiff, - FeeCalculator: feeCalculator, - Tx: testSubnet1, - } - require.NoError(testSubnet1.Unsigned.Visit(&executor)) + _, _, _, err = executor.StandardTx( + env.backend, + feeCalculator, + testSubnet1, + stateDiff, + ) + require.NoError(err) stateDiff.AddTx(testSubnet1, status.Committed) require.NoError(stateDiff.Apply(env.state)) diff --git a/vms/platformvm/block/executor/manager.go b/vms/platformvm/block/executor/manager.go index 5c419500e5f7..7f153a83be3f 100644 --- a/vms/platformvm/block/executor/manager.go +++ b/vms/platformvm/block/executor/manager.go @@ -142,12 +142,13 @@ func (m *manager) VerifyTx(tx *txs.Tx) error { } feeCalculator := state.PickFeeCalculator(m.txExecutorBackend.Config, stateDiff) - return tx.Unsigned.Visit(&executor.StandardTxExecutor{ - Backend: m.txExecutorBackend, - State: stateDiff, - FeeCalculator: feeCalculator, - Tx: tx, - }) + _, _, _, err = executor.StandardTx( + m.txExecutorBackend, + feeCalculator, + tx, + stateDiff, + ) + return err } func (m *manager) VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error { diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index dbff557ce263..80e925dbc192 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -517,30 +517,30 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator txfee.Calcula atomicRequests = make(map[ids.ID]*atomic.Requests) ) for _, tx := range txs { - txExecutor := executor.StandardTxExecutor{ - Backend: v.txExecutorBackend, - State: diff, - FeeCalculator: feeCalculator, - Tx: tx, - } - if err := tx.Unsigned.Visit(&txExecutor); err != nil { + txInputs, txAtomicRequests, onAccept, err := executor.StandardTx( + v.txExecutorBackend, + feeCalculator, + tx, + diff, + ) + if err != nil { txID := tx.ID() v.MarkDropped(txID, err) // cache tx as dropped return nil, nil, nil, err } // ensure it doesn't overlap with current input batch - if inputs.Overlaps(txExecutor.Inputs) { + if inputs.Overlaps(txInputs) { return nil, nil, nil, ErrConflictingBlockTxs } // Add UTXOs to batch - inputs.Union(txExecutor.Inputs) + inputs.Union(txInputs) diff.AddTx(tx, status.Committed) - if txExecutor.OnAccept != nil { - funcs = append(funcs, txExecutor.OnAccept) + if onAccept != nil { + funcs = append(funcs, onAccept) } - for chainID, txRequests := range txExecutor.AtomicRequests { + for chainID, txRequests := range txAtomicRequests { // Add/merge in the atomic requests represented by [tx] chainRequests, exists := atomicRequests[chainID] if !exists { diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 7f7b21671620..7a9f6b7086ab 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -14,6 +14,8 @@ import ( "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" @@ -41,7 +43,6 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool/mempoolmock" - "github.com/ava-labs/avalanchego/vms/platformvm/txs/txsmock" "github.com/ava-labs/avalanchego/vms/platformvm/txs/txstest" "github.com/ava-labs/avalanchego/vms/platformvm/utxo" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -51,24 +52,33 @@ import ( ) type testVerifierConfig struct { + DB database.Database Upgrades upgrade.Config + Context *snow.Context } func newTestVerifier(t testing.TB, c testVerifierConfig) *verifier { require := require.New(t) + if c.DB == nil { + c.DB = memdb.New() + } if c.Upgrades == (upgrade.Config{}) { c.Upgrades = upgradetest.GetConfig(upgradetest.Latest) } + if c.Context == nil { + c.Context = snowtest.Context(t, constants.PlatformChainID) + } mempool, err := mempool.New("", prometheus.NewRegistry(), nil) require.NoError(err) var ( state = statetest.New(t, statetest.Config{ + DB: c.DB, Upgrades: c.Upgrades, + Context: c.Context, }) - ctx = snowtest.Context(t, constants.PlatformChainID) clock = &mockable.Clock{} fx = &secp256k1fx.Fx{} ) @@ -83,7 +93,7 @@ func newTestVerifier(t testing.TB, c testVerifierConfig) *verifier { lastAccepted: state.GetLastAccepted(), blkIDToState: make(map[ids.ID]*blockState), state: state, - ctx: ctx, + ctx: c.Context, }, txExecutorBackend: &executor.Backend{ Config: &config.Config{ @@ -94,11 +104,11 @@ func newTestVerifier(t testing.TB, c testVerifierConfig) *verifier { SybilProtectionEnabled: true, UpgradeConfig: c.Upgrades, }, - Ctx: ctx, + Ctx: c.Context, Clk: clock, Fx: fx, FlowChecker: utxo.NewVerifier( - ctx, + c.Context, clock, fx, ), @@ -274,101 +284,148 @@ func TestVerifierVisitAtomicBlock(t *testing.T) { func TestVerifierVisitStandardBlock(t *testing.T) { require := require.New(t) - ctrl := gomock.NewController(t) - // Create mocked dependencies. - s := state.NewMockState(ctrl) - mempool := mempoolmock.NewMempool(ctrl) - parentID := ids.GenerateTestID() - parentStatelessBlk := block.NewMockBlock(ctrl) - parentState := state.NewMockDiff(ctrl) + var ( + ctx = snowtest.Context(t, constants.PlatformChainID) - backend := &backend{ - blkIDToState: map[ids.ID]*blockState{ - parentID: { - statelessBlock: parentStatelessBlk, - onAcceptState: parentState, + baseDB = memdb.New() + stateDB = prefixdb.New([]byte{0}, baseDB) + amDB = prefixdb.New([]byte{1}, baseDB) + + am = atomic.NewMemory(amDB) + sm = am.NewSharedMemory(ctx.ChainID) + xChainSM = am.NewSharedMemory(ctx.XChainID) + + owner = secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{genesis.EWOQKey.Address()}, + } + utxo = &avax.UTXO{ + UTXOID: avax.UTXOID{ + TxID: ids.GenerateTestID(), + OutputIndex: 1, }, - }, - Mempool: mempool, - state: s, - ctx: &snow.Context{ - Log: logging.NoLog{}, - }, - } - manager := &manager{ - txExecutorBackend: &executor.Backend{ - Config: &config.Config{ - UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), + Asset: avax.Asset{ID: ctx.AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: units.Avax, + OutputOwners: owner, }, - Clk: &mockable.Clock{}, - }, - backend: backend, - } + } + ) + + inputID := utxo.InputID() + utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo) + require.NoError(err) - blkTx := txsmock.NewUnsignedTx(ctrl) - atomicRequests := map[ids.ID]*atomic.Requests{ - ids.GenerateTestID(): { - RemoveRequests: [][]byte{{1}, {2}}, + require.NoError(xChainSM.Apply(map[ids.ID]*atomic.Requests{ + ctx.ChainID: { PutRequests: []*atomic.Element{ { - Key: []byte{3}, - Value: []byte{4}, - Traits: [][]byte{{5}, {6}}, + Key: inputID[:], + Value: utxoBytes, + Traits: [][]byte{ + genesis.EWOQKey.Address().Bytes(), + }, }, }, }, - } - blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.StandardTxExecutor{})).DoAndReturn( - func(e *executor.StandardTxExecutor) error { - e.OnAccept = func() {} - e.Inputs = set.Set[ids.ID]{} - e.AtomicRequests = atomicRequests - return nil - }, - ).Times(1) - - // We can't serialize [blkTx] because it isn't - // registered with the blocks.Codec. - // Serialize this block with a dummy tx - // and replace it after creation with the mock tx. - // TODO allow serialization of mock txs. - apricotBlk, err := block.NewApricotStandardBlock( - parentID, - 2, /*height*/ - []*txs.Tx{ - { - Unsigned: &txs.AdvanceTimeTx{}, - Creds: []verify.Verifiable{}, - }, - }, + })) + + ctx.SharedMemory = sm + + var ( + verifier = newTestVerifier(t, testVerifierConfig{ + DB: stateDB, + Upgrades: upgradetest.GetConfig(upgradetest.ApricotPhase5), + Context: ctx, + }) + wallet = txstest.NewWallet( + t, + verifier.ctx, + verifier.txExecutorBackend.Config, + verifier.state, + secp256k1fx.NewKeychain(genesis.EWOQKey), + nil, // subnetIDs + []ids.ID{ctx.XChainID}, // Read the UTXO to import + ) + initialTimestamp = verifier.state.GetTimestamp() + ) + + // Build the transaction that will be executed. + tx, err := wallet.IssueImportTx( + verifier.ctx.XChainID, + &owner, ) require.NoError(err) - apricotBlk.Transactions[0].Unsigned = blkTx - // Set expectations for dependencies. - timestamp := time.Now() - parentState.EXPECT().GetTimestamp().Return(timestamp).Times(1) - parentState.EXPECT().GetFeeState().Return(gas.State{}).Times(1) - parentState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) - parentState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) - parentState.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) - parentState.EXPECT().GetActiveSubnetOnlyValidatorsIterator().Return(&iterator.Empty[state.SubnetOnlyValidator]{}, nil).Times(1) - parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) - mempool.EXPECT().Remove(apricotBlk.Txs()).Times(1) + // Verify that the transaction is only consuming the imported UTXO. + require.Len(tx.InputIDs(), 1) - blk := manager.NewBlock(apricotBlk) - require.NoError(blk.Verify(context.Background())) + // Build the block that will be executed on top of the last accepted block. + lastAcceptedID := verifier.state.GetLastAccepted() + lastAccepted, err := verifier.state.GetStatelessBlock(lastAcceptedID) + require.NoError(err) - // Assert expected state. - require.Contains(manager.backend.blkIDToState, apricotBlk.ID()) - gotBlkState := manager.backend.blkIDToState[apricotBlk.ID()] - require.Equal(apricotBlk, gotBlkState.statelessBlock) - require.Equal(set.Set[ids.ID]{}, gotBlkState.inputs) - require.Equal(timestamp, gotBlkState.timestamp) + firstBlock, err := block.NewApricotStandardBlock( + lastAcceptedID, + lastAccepted.Height()+1, + []*txs.Tx{tx}, + ) + require.NoError(err) - // Visiting again should return nil without using dependencies. - require.NoError(blk.Verify(context.Background())) + // Execute the block. + require.NoError(firstBlock.Visit(verifier)) + + // Verify that the block's execution was recorded as expected. + firstBlockID := firstBlock.ID() + { + require.Contains(verifier.blkIDToState, firstBlockID) + atomicBlockState := verifier.blkIDToState[firstBlockID] + onAccept := atomicBlockState.onAcceptState + require.NotNil(onAccept) + + txID := tx.ID() + acceptedTx, acceptedStatus, err := onAccept.GetTx(txID) + require.NoError(err) + require.Equal(tx, acceptedTx) + require.Equal(status.Committed, acceptedStatus) + + require.Equal( + &blockState{ + statelessBlock: firstBlock, + + onAcceptState: onAccept, + + inputs: tx.InputIDs(), + timestamp: initialTimestamp, + atomicRequests: map[ids.ID]*atomic.Requests{ + verifier.ctx.XChainID: { + RemoveRequests: [][]byte{ + inputID[:], + }, + }, + }, + verifiedHeights: set.Of(uint64(0)), + }, + atomicBlockState, + ) + } + + // Verify that the import transaction can no be replayed. + { + secondBlock, err := block.NewApricotStandardBlock( + firstBlockID, + firstBlock.Height()+1, + []*txs.Tx{tx}, // Replay the prior transaction + ) + require.NoError(err) + + err = secondBlock.Visit(verifier) + require.ErrorIs(err, errConflictingParentTxs) + + // Verify that the block's execution was not recorded. + require.NotContains(verifier.blkIDToState, secondBlock.ID()) + } } func TestVerifierVisitCommitBlock(t *testing.T) { @@ -744,104 +801,6 @@ func TestBanffCommitBlockTimestampChecks(t *testing.T) { } } -func TestVerifierVisitStandardBlockWithDuplicateInputs(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - // Create mocked dependencies. - s := state.NewMockState(ctrl) - mempool := mempoolmock.NewMempool(ctrl) - - grandParentID := ids.GenerateTestID() - grandParentStatelessBlk := block.NewMockBlock(ctrl) - grandParentState := state.NewMockDiff(ctrl) - parentID := ids.GenerateTestID() - parentStatelessBlk := block.NewMockBlock(ctrl) - parentState := state.NewMockDiff(ctrl) - atomicInputs := set.Of(ids.GenerateTestID()) - - backend := &backend{ - blkIDToState: map[ids.ID]*blockState{ - grandParentID: { - statelessBlock: grandParentStatelessBlk, - onAcceptState: grandParentState, - inputs: atomicInputs, - }, - parentID: { - statelessBlock: parentStatelessBlk, - onAcceptState: parentState, - }, - }, - Mempool: mempool, - state: s, - ctx: &snow.Context{ - Log: logging.NoLog{}, - }, - } - verifier := &verifier{ - txExecutorBackend: &executor.Backend{ - Config: &config.Config{ - UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), - }, - Clk: &mockable.Clock{}, - }, - backend: backend, - } - - blkTx := txsmock.NewUnsignedTx(ctrl) - atomicRequests := map[ids.ID]*atomic.Requests{ - ids.GenerateTestID(): { - RemoveRequests: [][]byte{{1}, {2}}, - PutRequests: []*atomic.Element{ - { - Key: []byte{3}, - Value: []byte{4}, - Traits: [][]byte{{5}, {6}}, - }, - }, - }, - } - blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.StandardTxExecutor{})).DoAndReturn( - func(e *executor.StandardTxExecutor) error { - e.OnAccept = func() {} - e.Inputs = atomicInputs - e.AtomicRequests = atomicRequests - return nil - }, - ).Times(1) - - // We can't serialize [blkTx] because it isn't - // registered with the blocks.Codec. - // Serialize this block with a dummy tx - // and replace it after creation with the mock tx. - // TODO allow serialization of mock txs. - blk, err := block.NewApricotStandardBlock( - parentID, - 2, - []*txs.Tx{ - { - Unsigned: &txs.AdvanceTimeTx{}, - Creds: []verify.Verifiable{}, - }, - }, - ) - require.NoError(err) - blk.Transactions[0].Unsigned = blkTx - - // Set expectations for dependencies. - timestamp := time.Now() - parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) - parentState.EXPECT().GetTimestamp().Return(timestamp).Times(1) - parentState.EXPECT().GetFeeState().Return(gas.State{}).Times(1) - parentState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).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) - require.ErrorIs(err, errConflictingParentTxs) -} - func TestVerifierVisitApricotStandardBlockWithProposalBlockParent(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index 90eb1725e057..cc511109e68e 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -112,15 +112,15 @@ func (*atomicTxExecutor) ConvertSubnetTx(*txs.ConvertSubnetTx) error { return ErrWrongTxType } -func (e *atomicTxExecutor) ImportTx(tx *txs.ImportTx) error { - return e.atomicTx(tx) +func (e *atomicTxExecutor) ImportTx(*txs.ImportTx) error { + return e.atomicTx() } -func (e *atomicTxExecutor) ExportTx(tx *txs.ExportTx) error { - return e.atomicTx(tx) +func (e *atomicTxExecutor) ExportTx(*txs.ExportTx) error { + return e.atomicTx() } -func (e *atomicTxExecutor) atomicTx(tx txs.UnsignedTx) error { +func (e *atomicTxExecutor) atomicTx() error { onAccept, err := state.NewDiff( e.parentID, e.stateVersions, @@ -130,14 +130,13 @@ func (e *atomicTxExecutor) atomicTx(tx txs.UnsignedTx) error { } e.onAccept = onAccept - executor := StandardTxExecutor{ - Backend: e.backend, - State: e.onAccept, - FeeCalculator: e.feeCalculator, - Tx: e.tx, - } - err = tx.Visit(&executor) - e.inputs = executor.Inputs - e.atomicRequests = executor.AtomicRequests + inputs, atomicRequests, _, err := StandardTx( + e.backend, + e.feeCalculator, + e.tx, + e.onAccept, + ) + e.inputs = inputs + e.atomicRequests = atomicRequests return err } diff --git a/vms/platformvm/txs/executor/create_chain_test.go b/vms/platformvm/txs/executor/create_chain_test.go index 7f9919e4a8f5..f271b81dbebf 100644 --- a/vms/platformvm/txs/executor/create_chain_test.go +++ b/vms/platformvm/txs/executor/create_chain_test.go @@ -52,13 +52,12 @@ func TestCreateChainTxInsufficientControlSigs(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, stateDiff) - executor := StandardTxExecutor{ - Backend: &env.backend, - FeeCalculator: feeCalculator, - State: stateDiff, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + stateDiff, + ) require.ErrorIs(err, errUnauthorizedSubnetModification) } @@ -96,13 +95,12 @@ func TestCreateChainTxWrongControlSig(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, stateDiff) - executor := StandardTxExecutor{ - Backend: &env.backend, - FeeCalculator: feeCalculator, - State: stateDiff, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + stateDiff, + ) require.ErrorIs(err, errUnauthorizedSubnetModification) } @@ -137,13 +135,12 @@ func TestCreateChainTxNoSuchSubnet(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, builderDiff) - executor := StandardTxExecutor{ - Backend: &env.backend, - FeeCalculator: feeCalculator, - State: stateDiff, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + stateDiff, + ) require.ErrorIs(err, database.ErrNotFound) } @@ -175,13 +172,13 @@ func TestCreateChainTxValid(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, builderDiff) - executor := StandardTxExecutor{ - Backend: &env.backend, - FeeCalculator: feeCalculator, - State: stateDiff, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&executor)) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + stateDiff, + ) + require.NoError(err) } func TestCreateChainTxAP3FeeChange(t *testing.T) { @@ -248,13 +245,12 @@ func TestCreateChainTxAP3FeeChange(t *testing.T) { stateDiff.SetTimestamp(test.time) feeCalculator := state.PickFeeCalculator(env.config, stateDiff) - executor := StandardTxExecutor{ - Backend: &env.backend, - FeeCalculator: feeCalculator, - State: stateDiff, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + stateDiff, + ) require.ErrorIs(err, test.expectedError) }) } @@ -296,12 +292,11 @@ func TestEtnaCreateChainTxInvalidWithManagedSubnet(t *testing.T) { ) feeCalculator := state.PickFeeCalculator(env.config, builderDiff) - executor := StandardTxExecutor{ - Backend: &env.backend, - FeeCalculator: feeCalculator, - State: stateDiff, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + stateDiff, + ) require.ErrorIs(err, errIsImmutable) } diff --git a/vms/platformvm/txs/executor/create_subnet_test.go b/vms/platformvm/txs/executor/create_subnet_test.go index 0290d1811401..482e5175c945 100644 --- a/vms/platformvm/txs/executor/create_subnet_test.go +++ b/vms/platformvm/txs/executor/create_subnet_test.go @@ -79,13 +79,12 @@ func TestCreateSubnetTxAP3FeeChange(t *testing.T) { stateDiff.SetTimestamp(test.time) feeCalculator := state.PickFeeCalculator(env.config, stateDiff) - executor := StandardTxExecutor{ - Backend: &env.backend, - FeeCalculator: feeCalculator, - State: stateDiff, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + stateDiff, + ) require.ErrorIs(err, test.expectedErr) }) } diff --git a/vms/platformvm/txs/executor/export_test.go b/vms/platformvm/txs/executor/export_test.go index 34b1e8046454..3ecbc2e590bb 100644 --- a/vms/platformvm/txs/executor/export_test.go +++ b/vms/platformvm/txs/executor/export_test.go @@ -66,13 +66,13 @@ func TestNewExportTx(t *testing.T) { stateDiff.SetTimestamp(tt.timestamp) feeCalculator := state.PickFeeCalculator(env.config, stateDiff) - verifier := StandardTxExecutor{ - Backend: &env.backend, - FeeCalculator: feeCalculator, - State: stateDiff, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&verifier)) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + stateDiff, + ) + require.NoError(err) }) } } diff --git a/vms/platformvm/txs/executor/helpers_test.go b/vms/platformvm/txs/executor/helpers_test.go index 129e0d351f38..bf6a6f7214c1 100644 --- a/vms/platformvm/txs/executor/helpers_test.go +++ b/vms/platformvm/txs/executor/helpers_test.go @@ -220,13 +220,13 @@ func addSubnet(t *testing.T, env *environment) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, env.state) - executor := StandardTxExecutor{ - Backend: &env.backend, - FeeCalculator: feeCalculator, - State: stateDiff, - Tx: testSubnet1, - } - require.NoError(testSubnet1.Unsigned.Visit(&executor)) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + testSubnet1, + stateDiff, + ) + require.NoError(err) stateDiff.AddTx(testSubnet1, status.Committed) require.NoError(stateDiff.Apply(env.state)) diff --git a/vms/platformvm/txs/executor/import_test.go b/vms/platformvm/txs/executor/import_test.go index d3c48f17e186..f25a831bb3d4 100644 --- a/vms/platformvm/txs/executor/import_test.go +++ b/vms/platformvm/txs/executor/import_test.go @@ -156,13 +156,13 @@ func TestNewImportTx(t *testing.T) { stateDiff.SetTimestamp(tt.timestamp) feeCalculator := state.PickFeeCalculator(env.config, stateDiff) - verifier := StandardTxExecutor{ - Backend: &env.backend, - FeeCalculator: feeCalculator, - State: stateDiff, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&verifier)) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + stateDiff, + ) + require.NoError(err) }) } } diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 19fd07aac6d4..b2b6fdc825da 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -27,7 +27,7 @@ import ( ) var ( - _ txs.Visitor = (*StandardTxExecutor)(nil) + _ txs.Visitor = (*standardTxExecutor)(nil) errEmptyNodeID = errors.New("validator nodeID cannot be empty") errMaxStakeDurationTooLarge = errors.New("max stake duration must be less than or equal to the global max stake duration") @@ -37,37 +37,56 @@ var ( errMaxNumActiveValidators = errors.New("already at the max number of active validators") ) -type StandardTxExecutor struct { +func StandardTx( + backend *Backend, + feeCalculator fee.Calculator, + tx *txs.Tx, + state state.Diff, +) (set.Set[ids.ID], map[ids.ID]*atomic.Requests, func(), error) { + standardExecutor := standardTxExecutor{ + backend: backend, + feeCalculator: feeCalculator, + tx: tx, + state: state, + } + if err := tx.Unsigned.Visit(&standardExecutor); err != nil { + txID := tx.ID() + return nil, nil, nil, fmt.Errorf("standard tx %s failed execution: %w", txID, err) + } + return standardExecutor.inputs, standardExecutor.atomicRequests, standardExecutor.onAccept, nil +} + +type standardTxExecutor struct { // inputs, to be filled before visitor methods are called - *Backend - State state.Diff // state is expected to be modified - FeeCalculator fee.Calculator - Tx *txs.Tx + backend *Backend + state state.Diff // state is expected to be modified + feeCalculator fee.Calculator + tx *txs.Tx // outputs of visitor execution - OnAccept func() // may be nil - Inputs set.Set[ids.ID] - AtomicRequests map[ids.ID]*atomic.Requests // may be nil + onAccept func() // may be nil + inputs set.Set[ids.ID] + atomicRequests map[ids.ID]*atomic.Requests // may be nil } -func (*StandardTxExecutor) AdvanceTimeTx(*txs.AdvanceTimeTx) error { +func (*standardTxExecutor) AdvanceTimeTx(*txs.AdvanceTimeTx) error { return ErrWrongTxType } -func (*StandardTxExecutor) RewardValidatorTx(*txs.RewardValidatorTx) error { +func (*standardTxExecutor) RewardValidatorTx(*txs.RewardValidatorTx) error { return ErrWrongTxType } -func (e *StandardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { +func (e *standardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { if tx.Validator.NodeID == ids.EmptyNodeID { return errEmptyNodeID } if _, err := verifyAddValidatorTx( - e.Backend, - e.FeeCalculator, - e.State, - e.Tx, + e.backend, + e.feeCalculator, + e.state, + e.tx, tx, ); err != nil { return err @@ -77,12 +96,12 @@ func (e *StandardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { return err } - txID := e.Tx.ID() - avax.Consume(e.State, tx.Ins) - avax.Produce(e.State, txID, tx.Outs) + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) - if e.Config.PartialSyncPrimaryNetwork && tx.Validator.NodeID == e.Ctx.NodeID { - e.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", + if e.backend.Config.PartialSyncPrimaryNetwork && tx.Validator.NodeID == e.backend.Ctx.NodeID { + e.backend.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", zap.String("reason", "primary network is not being fully synced"), zap.Stringer("txID", txID), zap.String("txType", "addValidator"), @@ -92,12 +111,12 @@ func (e *StandardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { return nil } -func (e *StandardTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { +func (e *standardTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { if err := verifyAddSubnetValidatorTx( - e.Backend, - e.FeeCalculator, - e.State, - e.Tx, + e.backend, + e.feeCalculator, + e.state, + e.tx, tx, ); err != nil { return err @@ -107,18 +126,18 @@ func (e *StandardTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) return err } - txID := e.Tx.ID() - avax.Consume(e.State, tx.Ins) - avax.Produce(e.State, txID, tx.Outs) + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) return nil } -func (e *StandardTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { +func (e *standardTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { if _, err := verifyAddDelegatorTx( - e.Backend, - e.FeeCalculator, - e.State, - e.Tx, + e.backend, + e.feeCalculator, + e.state, + e.tx, tx, ); err != nil { return err @@ -128,146 +147,146 @@ func (e *StandardTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { return err } - txID := e.Tx.ID() - avax.Consume(e.State, tx.Ins) - avax.Produce(e.State, txID, tx.Outs) + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) return nil } -func (e *StandardTxExecutor) CreateChainTx(tx *txs.CreateChainTx) error { - if err := e.Tx.SyntacticVerify(e.Ctx); err != nil { +func (e *standardTxExecutor) CreateChainTx(tx *txs.CreateChainTx) error { + if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { return err } var ( - currentTimestamp = e.State.GetTimestamp() - isDurangoActive = e.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) + currentTimestamp = e.state.GetTimestamp() + isDurangoActive = e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) ) if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { return err } - baseTxCreds, err := verifyPoASubnetAuthorization(e.Backend, e.State, e.Tx, tx.SubnetID, tx.SubnetAuth) + baseTxCreds, err := verifyPoASubnetAuthorization(e.backend, e.state, e.tx, tx.SubnetID, tx.SubnetAuth) if err != nil { return err } // Verify the flowcheck - fee, err := e.FeeCalculator.CalculateFee(tx) + fee, err := e.feeCalculator.CalculateFee(tx) if err != nil { return err } - if err := e.FlowChecker.VerifySpend( + if err := e.backend.FlowChecker.VerifySpend( tx, - e.State, + e.state, tx.Ins, tx.Outs, baseTxCreds, map[ids.ID]uint64{ - e.Ctx.AVAXAssetID: fee, + e.backend.Ctx.AVAXAssetID: fee, }, ); err != nil { return err } - txID := e.Tx.ID() + txID := e.tx.ID() // Consume the UTXOS - avax.Consume(e.State, tx.Ins) + avax.Consume(e.state, tx.Ins) // Produce the UTXOS - avax.Produce(e.State, txID, tx.Outs) + avax.Produce(e.state, txID, tx.Outs) // Add the new chain to the database - e.State.AddChain(e.Tx) + e.state.AddChain(e.tx) // If this proposal is committed and this node is a member of the subnet // that validates the blockchain, create the blockchain - e.OnAccept = func() { - e.Config.CreateChain(txID, tx) + e.onAccept = func() { + e.backend.Config.CreateChain(txID, tx) } return nil } -func (e *StandardTxExecutor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { +func (e *standardTxExecutor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { // Make sure this transaction is well formed. - if err := e.Tx.SyntacticVerify(e.Ctx); err != nil { + if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { return err } var ( - currentTimestamp = e.State.GetTimestamp() - isDurangoActive = e.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) + currentTimestamp = e.state.GetTimestamp() + isDurangoActive = e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) ) if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { return err } // Verify the flowcheck - fee, err := e.FeeCalculator.CalculateFee(tx) + fee, err := e.feeCalculator.CalculateFee(tx) if err != nil { return err } - if err := e.FlowChecker.VerifySpend( + if err := e.backend.FlowChecker.VerifySpend( tx, - e.State, + e.state, tx.Ins, tx.Outs, - e.Tx.Creds, + e.tx.Creds, map[ids.ID]uint64{ - e.Ctx.AVAXAssetID: fee, + e.backend.Ctx.AVAXAssetID: fee, }, ); err != nil { return err } - txID := e.Tx.ID() + txID := e.tx.ID() // Consume the UTXOS - avax.Consume(e.State, tx.Ins) + avax.Consume(e.state, tx.Ins) // Produce the UTXOS - avax.Produce(e.State, txID, tx.Outs) + avax.Produce(e.state, txID, tx.Outs) // Add the new subnet to the database - e.State.AddSubnet(txID) - e.State.SetSubnetOwner(txID, tx.Owner) + e.state.AddSubnet(txID) + e.state.SetSubnetOwner(txID, tx.Owner) return nil } -func (e *StandardTxExecutor) ImportTx(tx *txs.ImportTx) error { - if err := e.Tx.SyntacticVerify(e.Ctx); err != nil { +func (e *standardTxExecutor) ImportTx(tx *txs.ImportTx) error { + if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { return err } var ( - currentTimestamp = e.State.GetTimestamp() - isDurangoActive = e.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) + currentTimestamp = e.state.GetTimestamp() + isDurangoActive = e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) ) if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { return err } - e.Inputs = set.NewSet[ids.ID](len(tx.ImportedInputs)) + e.inputs = set.NewSet[ids.ID](len(tx.ImportedInputs)) utxoIDs := make([][]byte, len(tx.ImportedInputs)) for i, in := range tx.ImportedInputs { utxoID := in.UTXOID.InputID() - e.Inputs.Add(utxoID) + e.inputs.Add(utxoID) utxoIDs[i] = utxoID[:] } // Skip verification of the shared memory inputs if the other primary // network chains are not guaranteed to be up-to-date. - if e.Bootstrapped.Get() && !e.Config.PartialSyncPrimaryNetwork { - if err := verify.SameSubnet(context.TODO(), e.Ctx, tx.SourceChain); err != nil { + if e.backend.Bootstrapped.Get() && !e.backend.Config.PartialSyncPrimaryNetwork { + if err := verify.SameSubnet(context.TODO(), e.backend.Ctx, tx.SourceChain); err != nil { return err } - allUTXOBytes, err := e.Ctx.SharedMemory.Get(tx.SourceChain, utxoIDs) + allUTXOBytes, err := e.backend.Ctx.SharedMemory.Get(tx.SourceChain, utxoIDs) if err != nil { return fmt.Errorf("failed to get shared memory: %w", err) } utxos := make([]*avax.UTXO, len(tx.Ins)+len(tx.ImportedInputs)) for index, input := range tx.Ins { - utxo, err := e.State.GetUTXO(input.InputID()) + utxo, err := e.state.GetUTXO(input.InputID()) if err != nil { return fmt.Errorf("failed to get UTXO %s: %w", &input.UTXOID, err) } @@ -286,35 +305,35 @@ func (e *StandardTxExecutor) ImportTx(tx *txs.ImportTx) error { copy(ins[len(tx.Ins):], tx.ImportedInputs) // Verify the flowcheck - fee, err := e.FeeCalculator.CalculateFee(tx) + fee, err := e.feeCalculator.CalculateFee(tx) if err != nil { return err } - if err := e.FlowChecker.VerifySpendUTXOs( + if err := e.backend.FlowChecker.VerifySpendUTXOs( tx, utxos, ins, tx.Outs, - e.Tx.Creds, + e.tx.Creds, map[ids.ID]uint64{ - e.Ctx.AVAXAssetID: fee, + e.backend.Ctx.AVAXAssetID: fee, }, ); err != nil { return err } } - txID := e.Tx.ID() + txID := e.tx.ID() // Consume the UTXOS - avax.Consume(e.State, tx.Ins) + avax.Consume(e.state, tx.Ins) // Produce the UTXOS - avax.Produce(e.State, txID, tx.Outs) + avax.Produce(e.state, txID, tx.Outs) // Note: We apply atomic requests even if we are not verifying atomic // requests to ensure the shared state will be correct if we later start // verifying the requests. - e.AtomicRequests = map[ids.ID]*atomic.Requests{ + e.atomicRequests = map[ids.ID]*atomic.Requests{ tx.SourceChain: { RemoveRequests: utxoIDs, }, @@ -322,14 +341,14 @@ func (e *StandardTxExecutor) ImportTx(tx *txs.ImportTx) error { return nil } -func (e *StandardTxExecutor) ExportTx(tx *txs.ExportTx) error { - if err := e.Tx.SyntacticVerify(e.Ctx); err != nil { +func (e *standardTxExecutor) ExportTx(tx *txs.ExportTx) error { + if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { return err } var ( - currentTimestamp = e.State.GetTimestamp() - isDurangoActive = e.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) + currentTimestamp = e.state.GetTimestamp() + isDurangoActive = e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) ) if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { return err @@ -339,36 +358,36 @@ func (e *StandardTxExecutor) ExportTx(tx *txs.ExportTx) error { copy(outs, tx.Outs) copy(outs[len(tx.Outs):], tx.ExportedOutputs) - if e.Bootstrapped.Get() { - if err := verify.SameSubnet(context.TODO(), e.Ctx, tx.DestinationChain); err != nil { + if e.backend.Bootstrapped.Get() { + if err := verify.SameSubnet(context.TODO(), e.backend.Ctx, tx.DestinationChain); err != nil { return err } } // Verify the flowcheck - fee, err := e.FeeCalculator.CalculateFee(tx) + fee, err := e.feeCalculator.CalculateFee(tx) if err != nil { return err } - if err := e.FlowChecker.VerifySpend( + if err := e.backend.FlowChecker.VerifySpend( tx, - e.State, + e.state, tx.Ins, outs, - e.Tx.Creds, + e.tx.Creds, map[ids.ID]uint64{ - e.Ctx.AVAXAssetID: fee, + e.backend.Ctx.AVAXAssetID: fee, }, ); err != nil { return fmt.Errorf("failed verifySpend: %w", err) } - txID := e.Tx.ID() + txID := e.tx.ID() // Consume the UTXOS - avax.Consume(e.State, tx.Ins) + avax.Consume(e.state, tx.Ins) // Produce the UTXOS - avax.Produce(e.State, txID, tx.Outs) + avax.Produce(e.state, txID, tx.Outs) // Note: We apply atomic requests even if we are not verifying atomic // requests to ensure the shared state will be correct if we later start @@ -399,7 +418,7 @@ func (e *StandardTxExecutor) ExportTx(tx *txs.ExportTx) error { elems[i] = elem } - e.AtomicRequests = map[ids.ID]*atomic.Requests{ + e.atomicRequests = map[ids.ID]*atomic.Requests{ tx.DestinationChain: { PutRequests: elems, }, @@ -412,12 +431,12 @@ func (e *StandardTxExecutor) ExportTx(tx *txs.ExportTx) error { // transaction will result in [tx.NodeID] being removed as a validator of // [tx.SubnetID]. // Note: [tx.NodeID] may be either a current or pending validator. -func (e *StandardTxExecutor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { +func (e *standardTxExecutor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { staker, isCurrentValidator, err := verifyRemoveSubnetValidatorTx( - e.Backend, - e.FeeCalculator, - e.State, - e.Tx, + e.backend, + e.feeCalculator, + e.state, + e.tx, tx, ) if err != nil { @@ -425,55 +444,55 @@ func (e *StandardTxExecutor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidat } if isCurrentValidator { - e.State.DeleteCurrentValidator(staker) + e.state.DeleteCurrentValidator(staker) } else { - e.State.DeletePendingValidator(staker) + e.state.DeletePendingValidator(staker) } // Invariant: There are no permissioned subnet delegators to remove. - txID := e.Tx.ID() - avax.Consume(e.State, tx.Ins) - avax.Produce(e.State, txID, tx.Outs) + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) return nil } -func (e *StandardTxExecutor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { - currentTimestamp := e.State.GetTimestamp() - if e.Config.UpgradeConfig.IsEtnaActivated(currentTimestamp) { +func (e *standardTxExecutor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { + currentTimestamp := e.state.GetTimestamp() + if e.backend.Config.UpgradeConfig.IsEtnaActivated(currentTimestamp) { return errTransformSubnetTxPostEtna } - if err := e.Tx.SyntacticVerify(e.Ctx); err != nil { + if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { return err } - isDurangoActive := e.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) + isDurangoActive := e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { return err } // Note: math.MaxInt32 * time.Second < math.MaxInt64 - so this can never // overflow. - if time.Duration(tx.MaxStakeDuration)*time.Second > e.Backend.Config.MaxStakeDuration { + if time.Duration(tx.MaxStakeDuration)*time.Second > e.backend.Config.MaxStakeDuration { return errMaxStakeDurationTooLarge } - baseTxCreds, err := verifyPoASubnetAuthorization(e.Backend, e.State, e.Tx, tx.Subnet, tx.SubnetAuth) + baseTxCreds, err := verifyPoASubnetAuthorization(e.backend, e.state, e.tx, tx.Subnet, tx.SubnetAuth) if err != nil { return err } // Verify the flowcheck - fee, err := e.FeeCalculator.CalculateFee(tx) + fee, err := e.feeCalculator.CalculateFee(tx) if err != nil { return err } totalRewardAmount := tx.MaximumSupply - tx.InitialSupply - if err := e.Backend.FlowChecker.VerifySpend( + if err := e.backend.FlowChecker.VerifySpend( tx, - e.State, + e.state, tx.Ins, tx.Outs, baseTxCreds, @@ -481,31 +500,31 @@ func (e *StandardTxExecutor) TransformSubnetTx(tx *txs.TransformSubnetTx) error // entry in this map literal from being overwritten by the // second entry. map[ids.ID]uint64{ - e.Ctx.AVAXAssetID: fee, - tx.AssetID: totalRewardAmount, + e.backend.Ctx.AVAXAssetID: fee, + tx.AssetID: totalRewardAmount, }, ); err != nil { return err } - txID := e.Tx.ID() + txID := e.tx.ID() // Consume the UTXOS - avax.Consume(e.State, tx.Ins) + avax.Consume(e.state, tx.Ins) // Produce the UTXOS - avax.Produce(e.State, txID, tx.Outs) + avax.Produce(e.state, txID, tx.Outs) // Transform the new subnet in the database - e.State.AddSubnetTransformation(e.Tx) - e.State.SetCurrentSupply(tx.Subnet, tx.InitialSupply) + e.state.AddSubnetTransformation(e.tx) + e.state.SetCurrentSupply(tx.Subnet, tx.InitialSupply) return nil } -func (e *StandardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { +func (e *standardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { if err := verifyAddPermissionlessValidatorTx( - e.Backend, - e.FeeCalculator, - e.State, - e.Tx, + e.backend, + e.feeCalculator, + e.state, + e.tx, tx, ); err != nil { return err @@ -515,14 +534,14 @@ func (e *StandardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionl return err } - txID := e.Tx.ID() - avax.Consume(e.State, tx.Ins) - avax.Produce(e.State, txID, tx.Outs) + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) - if e.Config.PartialSyncPrimaryNetwork && + if e.backend.Config.PartialSyncPrimaryNetwork && tx.Subnet == constants.PrimaryNetworkID && - tx.Validator.NodeID == e.Ctx.NodeID { - e.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", + tx.Validator.NodeID == e.backend.Ctx.NodeID { + e.backend.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", zap.String("reason", "primary network is not being fully synced"), zap.Stringer("txID", txID), zap.String("txType", "addPermissionlessValidator"), @@ -533,12 +552,12 @@ func (e *StandardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionl return nil } -func (e *StandardTxExecutor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { +func (e *standardTxExecutor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { if err := verifyAddPermissionlessDelegatorTx( - e.Backend, - e.FeeCalculator, - e.State, - e.Tx, + e.backend, + e.feeCalculator, + e.state, + e.tx, tx, ); err != nil { return err @@ -548,9 +567,9 @@ func (e *StandardTxExecutor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionl return err } - txID := e.Tx.ID() - avax.Consume(e.State, tx.Ins) - avax.Produce(e.State, txID, tx.Outs) + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) return nil } @@ -558,37 +577,37 @@ func (e *StandardTxExecutor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionl // [e.State]. For verification rules, see [verifyTransferSubnetOwnershipTx]. // This transaction will result in the ownership of [tx.Subnet] being transferred // to [tx.Owner]. -func (e *StandardTxExecutor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { +func (e *standardTxExecutor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { err := verifyTransferSubnetOwnershipTx( - e.Backend, - e.FeeCalculator, - e.State, - e.Tx, + e.backend, + e.feeCalculator, + e.state, + e.tx, tx, ) if err != nil { return err } - e.State.SetSubnetOwner(tx.Subnet, tx.Owner) + e.state.SetSubnetOwner(tx.Subnet, tx.Owner) - txID := e.Tx.ID() - avax.Consume(e.State, tx.Ins) - avax.Produce(e.State, txID, tx.Outs) + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) return nil } -func (e *StandardTxExecutor) BaseTx(tx *txs.BaseTx) error { +func (e *standardTxExecutor) BaseTx(tx *txs.BaseTx) error { var ( - currentTimestamp = e.State.GetTimestamp() - upgrades = e.Backend.Config.UpgradeConfig + currentTimestamp = e.state.GetTimestamp() + upgrades = e.backend.Config.UpgradeConfig ) if !upgrades.IsDurangoActivated(currentTimestamp) { return ErrDurangoUpgradeNotActive } // Verify the tx is well-formed - if err := e.Tx.SyntacticVerify(e.Ctx); err != nil { + if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { return err } @@ -597,41 +616,41 @@ func (e *StandardTxExecutor) BaseTx(tx *txs.BaseTx) error { } // Verify the flowcheck - fee, err := e.FeeCalculator.CalculateFee(tx) + fee, err := e.feeCalculator.CalculateFee(tx) if err != nil { return err } - if err := e.FlowChecker.VerifySpend( + if err := e.backend.FlowChecker.VerifySpend( tx, - e.State, + e.state, tx.Ins, tx.Outs, - e.Tx.Creds, + e.tx.Creds, map[ids.ID]uint64{ - e.Ctx.AVAXAssetID: fee, + e.backend.Ctx.AVAXAssetID: fee, }, ); err != nil { return err } - txID := e.Tx.ID() + txID := e.tx.ID() // Consume the UTXOS - avax.Consume(e.State, tx.Ins) + avax.Consume(e.state, tx.Ins) // Produce the UTXOS - avax.Produce(e.State, txID, tx.Outs) + avax.Produce(e.state, txID, tx.Outs) return nil } -func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { +func (e *standardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { var ( - currentTimestamp = e.State.GetTimestamp() - upgrades = e.Backend.Config.UpgradeConfig + currentTimestamp = e.state.GetTimestamp() + upgrades = e.backend.Config.UpgradeConfig ) if !upgrades.IsEtnaActivated(currentTimestamp) { return errEtnaUpgradeNotActive } - if err := e.Tx.SyntacticVerify(e.Ctx); err != nil { + if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { return err } @@ -639,20 +658,20 @@ 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.backend, e.state, e.tx, tx.Subnet, tx.SubnetAuth) if err != nil { return err } // Verify the flowcheck - fee, err := e.FeeCalculator.CalculateFee(tx) + fee, err := e.feeCalculator.CalculateFee(tx) if err != nil { return err } var ( startTime = uint64(currentTimestamp.Unix()) - currentFees = e.State.GetAccruedFees() + currentFees = e.state.GetAccruedFees() subnetConversionData = message.SubnetConversionData{ SubnetID: tx.Subnet, ManagerChainID: tx.ChainID, @@ -689,7 +708,7 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { } if vdr.Balance != 0 { // We are attempting to add an active validator - if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeConfig.Capacity { + if gas.Gas(e.state.NumActiveSubnetOnlyValidators()) >= e.backend.Config.ValidatorFeeConfig.Capacity { return errMaxNumActiveValidators } @@ -704,7 +723,7 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { } } - if err := e.State.PutSubnetOnlyValidator(sov); err != nil { + if err := e.state.PutSubnetOnlyValidator(sov); err != nil { return err } @@ -714,14 +733,14 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { Weight: vdr.Weight, } } - if err := e.Backend.FlowChecker.VerifySpend( + if err := e.backend.FlowChecker.VerifySpend( tx, - e.State, + e.state, tx.Ins, tx.Outs, baseTxCreds, map[ids.ID]uint64{ - e.Ctx.AVAXAssetID: fee, + e.backend.Ctx.AVAXAssetID: fee, }, ); err != nil { return err @@ -732,14 +751,14 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { return err } - txID := e.Tx.ID() + txID := e.tx.ID() // Consume the UTXOS - avax.Consume(e.State, tx.Ins) + avax.Consume(e.state, tx.Ins) // Produce the UTXOS - avax.Produce(e.State, txID, tx.Outs) + avax.Produce(e.state, txID, tx.Outs) // Track the subnet conversion in the database - e.State.SetSubnetConversion( + e.state.SetSubnetConversion( tx.Subnet, state.SubnetConversion{ ConversionID: conversionID, @@ -751,15 +770,15 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { } // Creates the staker as defined in [stakerTx] and adds it to [e.State]. -func (e *StandardTxExecutor) putStaker(stakerTx txs.Staker) error { +func (e *standardTxExecutor) putStaker(stakerTx txs.Staker) error { var ( - chainTime = e.State.GetTimestamp() - txID = e.Tx.ID() + chainTime = e.state.GetTimestamp() + txID = e.tx.ID() staker *state.Staker err error ) - if !e.Config.UpgradeConfig.IsDurangoActivated(chainTime) { + if !e.backend.Config.UpgradeConfig.IsDurangoActivated(chainTime) { // Pre-Durango, stakers set a future [StartTime] and are added to the // pending staker set. They are promoted to the current staker set once // the chain time reaches [StartTime]. @@ -775,12 +794,12 @@ func (e *StandardTxExecutor) putStaker(stakerTx txs.Staker) error { var potentialReward uint64 if !stakerTx.CurrentPriority().IsPermissionedValidator() { subnetID := stakerTx.SubnetID() - currentSupply, err := e.State.GetCurrentSupply(subnetID) + currentSupply, err := e.state.GetCurrentSupply(subnetID) if err != nil { return err } - rewards, err := GetRewardsCalculator(e.Backend, e.State, subnetID) + rewards, err := GetRewardsCalculator(e.backend, e.state, subnetID) if err != nil { return err } @@ -794,7 +813,7 @@ func (e *StandardTxExecutor) putStaker(stakerTx txs.Staker) error { currentSupply, ) - e.State.SetCurrentSupply(subnetID, currentSupply+potentialReward) + e.state.SetCurrentSupply(subnetID, currentSupply+potentialReward) } staker, err = state.NewCurrentStaker(txID, stakerTx, chainTime, potentialReward) @@ -805,17 +824,17 @@ func (e *StandardTxExecutor) putStaker(stakerTx txs.Staker) error { switch priority := staker.Priority; { case priority.IsCurrentValidator(): - if err := e.State.PutCurrentValidator(staker); err != nil { + if err := e.state.PutCurrentValidator(staker); err != nil { return err } case priority.IsCurrentDelegator(): - e.State.PutCurrentDelegator(staker) + e.state.PutCurrentDelegator(staker) case priority.IsPendingValidator(): - if err := e.State.PutPendingValidator(staker); err != nil { + if err := e.state.PutPendingValidator(staker); err != nil { return err } case priority.IsPendingDelegator(): - e.State.PutPendingDelegator(staker) + e.state.PutPendingDelegator(staker) default: return fmt.Errorf("staker %s, unexpected priority %d", staker.TxID, priority) } diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 1080013ed776..8a2e574ccfc5 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -101,13 +101,12 @@ func TestStandardTxExecutorAddValidatorTxEmptyID(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, stateDiff) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: stateDiff, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + stateDiff, + ) require.ErrorIs(err, errEmptyNodeID) } } @@ -353,13 +352,12 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { env.config.UpgradeConfig.BanffTime = onAcceptState.GetTimestamp() feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, tt.expectedExecutionErr) }) } @@ -400,13 +398,12 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, ErrPeriodMismatch) } @@ -435,13 +432,13 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&executor)) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) + require.NoError(err) } // Add a validator to pending validator set of primary network @@ -488,13 +485,12 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, ErrNotValidator) } @@ -538,13 +534,12 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, ErrPeriodMismatch) } @@ -571,13 +566,12 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, ErrPeriodMismatch) } @@ -604,13 +598,13 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - require.NoError(tx.Unsigned.Visit(&executor)) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) + require.NoError(err) } // Case: Proposed validator start validating at/before current timestamp @@ -639,13 +633,12 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, ErrTimestampNotBeforeStartTime) } @@ -707,13 +700,12 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, ErrDuplicateValidator) } @@ -751,13 +743,12 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, secp256k1fx.ErrInputIndicesNotSortedUnique) } @@ -791,13 +782,12 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, errUnauthorizedSubnetModification) } @@ -829,13 +819,12 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, errUnauthorizedSubnetModification) } @@ -877,13 +866,12 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, ErrDuplicateValidator) } } @@ -925,12 +913,13 @@ func TestEtnaStandardTxExecutorAddSubnetValidator(t *testing.T) { }, ) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, errIsImmutable) } @@ -965,13 +954,12 @@ func TestBanffStandardTxExecutorAddValidator(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, ErrTimestampNotBeforeStartTime) } @@ -1008,13 +996,12 @@ func TestBanffStandardTxExecutorAddValidator(t *testing.T) { onAcceptState.AddTx(tx, status.Committed) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, ErrAlreadyValidator) } @@ -1048,13 +1035,12 @@ func TestBanffStandardTxExecutorAddValidator(t *testing.T) { onAcceptState.AddTx(tx, status.Committed) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, ErrAlreadyValidator) } @@ -1089,13 +1075,12 @@ func TestBanffStandardTxExecutorAddValidator(t *testing.T) { } feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - executor := StandardTxExecutor{ - Backend: &env.backend, - FeeCalculator: feeCalculator, - State: onAcceptState, - Tx: tx, - } - err = tx.Unsigned.Visit(&executor) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, ErrFlowCheckFailed) } } @@ -1188,14 +1173,13 @@ func TestDurangoDisabledTransactions(t *testing.T) { require.NoError(err) tx := tt.buildTx(t, env) - feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - err = tx.Unsigned.Visit(&StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - }) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, tt.expectedErr) }) } @@ -1401,12 +1385,13 @@ func TestDurangoMemoField(t *testing.T) { require.NoError(err) feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) - require.NoError(subnetValTx.Unsigned.Visit(&StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: subnetValTx, - })) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + subnetValTx, + onAcceptState, + ) + require.NoError(err) tx, err := wallet.IssueRemoveSubnetValidatorTx( primaryValidator.NodeID, @@ -1592,22 +1577,23 @@ func TestDurangoMemoField(t *testing.T) { // Populated memo field should error tx, onAcceptState := tt.setupTest(t, env, []byte{'m', 'e', 'm', 'o'}) - err := tx.Unsigned.Visit(&StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - }) + _, _, _, err := StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, avax.ErrMemoTooLarge) // Empty memo field should not error tx, onAcceptState = tt.setupTest(t, env, []byte{}) - require.NoError(tx.Unsigned.Visit(&StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - FeeCalculator: feeCalculator, - Tx: tx, - })) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) + require.NoError(err) }) } } @@ -1623,15 +1609,16 @@ func TestEtnaDisabledTransactions(t *testing.T) { onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) require.NoError(err) + feeCalculator := state.PickFeeCalculator(env.config, env.state) tx := &txs.Tx{ Unsigned: &txs.TransformSubnetTx{}, } - - err = tx.Unsigned.Visit(&StandardTxExecutor{ - Backend: &env.backend, - State: onAcceptState, - Tx: tx, - }) + _, _, _, err = StandardTx( + &env.backend, + feeCalculator, + tx, + onAcceptState, + ) require.ErrorIs(err, errTransformSubnetTxPostEtna) } @@ -1735,14 +1722,14 @@ func newValidRemoveSubnetValidatorTxVerifyEnv(t *testing.T, ctrl *gomock.Control func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { type test struct { name string - newExecutor func(*gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) + newExecutor func(*gomock.Controller) (*txs.RemoveSubnetValidatorTx, *standardTxExecutor) expectedErr error } tests := []test{ { name: "valid tx", - newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *standardTxExecutor) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) // Set dependency expectations. @@ -1774,26 +1761,25 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Etna, env.latestForkTime), } feeCalculator := state.NewStaticFeeCalculator(cfg, env.state.GetTimestamp()) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, expectedErr: nil, }, { name: "tx fails syntactic verification", - newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *standardTxExecutor) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) // Setting the subnet ID to the Primary Network ID makes the tx fail syntactic verification env.tx.Unsigned.(*txs.RemoveSubnetValidatorTx).Subnet = constants.PrimaryNetworkID @@ -1804,26 +1790,25 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, expectedErr: txs.ErrRemovePrimaryNetworkValidator, }, { name: "node isn't a validator of the subnet", - newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *standardTxExecutor) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) env.state = state.NewMockDiff(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() @@ -1834,26 +1819,25 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, expectedErr: ErrNotValidator, }, { name: "validator is permissionless", - newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *standardTxExecutor) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) staker := *env.staker @@ -1867,26 +1851,25 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, expectedErr: ErrRemovePermissionlessValidator, }, { name: "tx has no credentials", - newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *standardTxExecutor) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) // Remove credentials env.tx.Creds = nil @@ -1898,26 +1881,25 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, expectedErr: errWrongNumberOfCredentials, }, { name: "can't find subnet", - newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *standardTxExecutor) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) env.state = state.NewMockDiff(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() @@ -1928,26 +1910,25 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, expectedErr: database.ErrNotFound, }, { name: "no permission to remove validator", - newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *standardTxExecutor) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) env.state = state.NewMockDiff(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() @@ -1960,26 +1941,25 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, expectedErr: errUnauthorizedSubnetModification, }, { name: "flow checker failed", - newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *standardTxExecutor) { env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) env.state = state.NewMockDiff(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() @@ -1995,19 +1975,18 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, expectedErr: ErrFlowCheckFailed, @@ -2137,14 +2116,14 @@ func newValidTransformSubnetTxVerifyEnv(t *testing.T, ctrl *gomock.Controller) t func TestStandardExecutorTransformSubnetTx(t *testing.T) { type test struct { name string - newExecutor func(*gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) + newExecutor func(*gomock.Controller) (*txs.TransformSubnetTx, *standardTxExecutor) err error } tests := []test{ { name: "tx fails syntactic verification", - newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *standardTxExecutor) { env := newValidTransformSubnetTxVerifyEnv(t, ctrl) // Setting the tx to nil makes the tx fail syntactic verification env.tx.Unsigned = (*txs.TransformSubnetTx)(nil) @@ -2155,26 +2134,25 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, err: txs.ErrNilTx, }, { name: "max stake duration too large", - newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *standardTxExecutor) { env := newValidTransformSubnetTxVerifyEnv(t, ctrl) env.unsignedTx.MaxStakeDuration = math.MaxUint32 env.state = state.NewMockDiff(ctrl) @@ -2184,26 +2162,25 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, err: errMaxStakeDurationTooLarge, }, { name: "fail subnet authorization", - newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *standardTxExecutor) { env := newValidTransformSubnetTxVerifyEnv(t, ctrl) // Remove credentials env.tx.Creds = nil @@ -2216,26 +2193,25 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { } feeCalculator := state.PickFeeCalculator(cfg, env.state) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, err: errWrongNumberOfCredentials, }, { name: "flow checker failed", - newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *standardTxExecutor) { env := newValidTransformSubnetTxVerifyEnv(t, ctrl) env.state = state.NewMockDiff(ctrl) subnetOwner := fxmock.NewOwner(ctrl) @@ -2257,26 +2233,25 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { } feeCalculator := state.PickFeeCalculator(cfg, env.state) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, err: ErrFlowCheckFailed, }, { name: "invalid after subnet conversion", - newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *standardTxExecutor) { env := newValidTransformSubnetTxVerifyEnv(t, ctrl) // Set dependency expectations. @@ -2299,26 +2274,25 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { MaxStakeDuration: math.MaxInt64, } feeCalculator := state.PickFeeCalculator(cfg, env.state) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, err: errIsImmutable, }, { name: "valid tx", - newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) { + newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *standardTxExecutor) { env := newValidTransformSubnetTxVerifyEnv(t, ctrl) // Set dependency expectations. @@ -2345,19 +2319,18 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { } feeCalculator := state.PickFeeCalculator(cfg, env.state) - e := &StandardTxExecutor{ - Backend: &Backend{ + e := &standardTxExecutor{ + backend: &Backend{ Config: cfg, - Bootstrapped: &utils.Atomic[bool]{}, + Bootstrapped: utils.NewAtomic(true), Fx: env.fx, FlowChecker: env.flowChecker, Ctx: &snow.Context{}, }, - FeeCalculator: feeCalculator, - Tx: env.tx, - State: env.state, + feeCalculator: feeCalculator, + tx: env.tx, + state: env.state, } - e.Bootstrapped.Set(true) return env.unsignedTx, e }, err: nil, @@ -2419,18 +2392,19 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { diff, err := state.NewDiffOn(baseState) require.NoError(t, err) - require.NoError(t, createSubnetTx.Unsigned.Visit(&StandardTxExecutor{ - Backend: &Backend{ + _, _, _, err = StandardTx( + &Backend{ Config: defaultConfig, Bootstrapped: utils.NewAtomic(true), Fx: fx, FlowChecker: flowChecker, Ctx: ctx, }, - FeeCalculator: state.PickFeeCalculator(defaultConfig, baseState), - Tx: createSubnetTx, - State: diff, - })) + state.PickFeeCalculator(defaultConfig, baseState), + createSubnetTx, + diff, + ) + require.NoError(t, err) require.NoError(t, diff.Apply(baseState)) require.NoError(t, baseState.Commit()) @@ -2441,13 +2415,13 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { tests := []struct { name string builderOptions []common.Option - updateExecutor func(executor *StandardTxExecutor) error + updateExecutor func(executor *standardTxExecutor) error expectedErr error }{ { name: "invalid prior to E-Upgrade", - updateExecutor: func(e *StandardTxExecutor) error { - e.Backend.Config = &config.Config{ + updateExecutor: func(e *standardTxExecutor) error { + e.backend.Config = &config.Config{ UpgradeConfig: upgradetest.GetConfig(upgradetest.Durango), } return nil @@ -2456,8 +2430,8 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { }, { name: "tx fails syntactic verification", - updateExecutor: func(e *StandardTxExecutor) error { - e.Backend.Ctx = snowtest.Context(t, ids.GenerateTestID()) + updateExecutor: func(e *standardTxExecutor) error { + e.backend.Ctx = snowtest.Context(t, ids.GenerateTestID()) return nil }, expectedErr: avax.ErrWrongChainID, @@ -2471,8 +2445,8 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { }, { name: "fail subnet authorization", - updateExecutor: func(e *StandardTxExecutor) error { - e.State.SetSubnetOwner(subnetID, &secp256k1fx.OutputOwners{ + updateExecutor: func(e *standardTxExecutor) error { + e.state.SetSubnetOwner(subnetID, &secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{ ids.GenerateTestShortID(), @@ -2484,8 +2458,8 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { }, { name: "invalid if subnet is transformed", - updateExecutor: func(e *StandardTxExecutor) error { - e.State.AddSubnetTransformation(&txs.Tx{Unsigned: &txs.TransformSubnetTx{ + updateExecutor: func(e *standardTxExecutor) error { + e.state.AddSubnetTransformation(&txs.Tx{Unsigned: &txs.TransformSubnetTx{ Subnet: subnetID, }}) return nil @@ -2494,8 +2468,8 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { }, { name: "invalid if subnet is converted", - updateExecutor: func(e *StandardTxExecutor) error { - e.State.SetSubnetConversion( + updateExecutor: func(e *standardTxExecutor) error { + e.state.SetSubnetConversion( subnetID, state.SubnetConversion{ ConversionID: ids.GenerateTestID(), @@ -2509,16 +2483,16 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { }, { name: "invalid fee calculation", - updateExecutor: func(e *StandardTxExecutor) error { - e.FeeCalculator = txfee.NewStaticCalculator(e.Config.StaticFeeConfig) + updateExecutor: func(e *standardTxExecutor) error { + e.feeCalculator = txfee.NewStaticCalculator(e.backend.Config.StaticFeeConfig) return nil }, expectedErr: txfee.ErrUnsupportedTx, }, { name: "too many active validators", - updateExecutor: func(e *StandardTxExecutor) error { - e.Backend.Config = &config.Config{ + updateExecutor: func(e *standardTxExecutor) error { + e.backend.Config = &config.Config{ DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, ValidatorFeeConfig: validatorfee.Config{ Capacity: 0, @@ -2534,8 +2508,8 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { }, { name: "invalid subnet only validator", - updateExecutor: func(e *StandardTxExecutor) error { - return e.State.PutSubnetOnlyValidator(state.SubnetOnlyValidator{ + updateExecutor: func(e *standardTxExecutor) error { + return e.state.PutSubnetOnlyValidator(state.SubnetOnlyValidator{ ValidationID: ids.GenerateTestID(), SubnetID: subnetID, NodeID: nodeID, @@ -2546,9 +2520,9 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { }, { name: "insufficient fee", - updateExecutor: func(e *StandardTxExecutor) error { - e.FeeCalculator = txfee.NewDynamicCalculator( - e.Config.DynamicFeeConfig.Weights, + updateExecutor: func(e *standardTxExecutor) error { + e.feeCalculator = txfee.NewDynamicCalculator( + e.backend.Config.DynamicFeeConfig.Weights, 100*genesis.LocalParams.DynamicFeeConfig.MinPrice, ) return nil @@ -2607,17 +2581,17 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { diff, err := state.NewDiffOn(baseState) require.NoError(err) - executor := &StandardTxExecutor{ - Backend: &Backend{ + executor := &standardTxExecutor{ + backend: &Backend{ Config: defaultConfig, Bootstrapped: utils.NewAtomic(true), Fx: fx, FlowChecker: flowChecker, Ctx: ctx, }, - FeeCalculator: state.PickFeeCalculator(defaultConfig, baseState), - Tx: convertSubnetTx, - State: diff, + feeCalculator: state.PickFeeCalculator(defaultConfig, baseState), + tx: convertSubnetTx, + state: diff, } if test.updateExecutor != nil { require.NoError(test.updateExecutor(executor)) From 3a01924c0d92c8f7eb599ce38e195d8fea67a331 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 11:06:08 -0500 Subject: [PATCH 315/400] nit --- vms/platformvm/block/executor/verifier_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 7a9f6b7086ab..9dfc631a4915 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -411,7 +411,7 @@ func TestVerifierVisitStandardBlock(t *testing.T) { ) } - // Verify that the import transaction can no be replayed. + // Verify that the import transaction can not be replayed. { secondBlock, err := block.NewApricotStandardBlock( firstBlockID, From d7e00ddf76346253c824dfa3511acd75ddf3033c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 11:08:53 -0500 Subject: [PATCH 316/400] nit --- vms/platformvm/txs/executor/atomic_tx_executor.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index cc511109e68e..0545bc476d8b 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -128,14 +128,14 @@ func (e *atomicTxExecutor) atomicTx() error { if err != nil { return err } - e.onAccept = onAccept inputs, atomicRequests, _, err := StandardTx( e.backend, e.feeCalculator, e.tx, - e.onAccept, + onAccept, ) + e.onAccept = onAccept e.inputs = inputs e.atomicRequests = atomicRequests return err From 56b00498c38345bc8cc6876e03835a5dc301df7d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 11:09:29 -0500 Subject: [PATCH 317/400] nit --- vms/platformvm/txs/executor/atomic_tx_executor.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index 0545bc476d8b..a4535a1d1494 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -129,14 +129,12 @@ func (e *atomicTxExecutor) atomicTx() error { return err } - inputs, atomicRequests, _, err := StandardTx( + e.onAccept = onAccept + e.inputs, e.atomicRequests, _, err = StandardTx( e.backend, e.feeCalculator, e.tx, onAccept, ) - e.onAccept = onAccept - e.inputs = inputs - e.atomicRequests = atomicRequests return err } From 3540203f62e2fe7a8e643b4469fdb0590a213be8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 11:10:28 -0500 Subject: [PATCH 318/400] nit --- vms/platformvm/block/builder/builder.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vms/platformvm/block/builder/builder.go b/vms/platformvm/block/builder/builder.go index b57cc9ecb4e5..40ce8a3633c7 100644 --- a/vms/platformvm/block/builder/builder.go +++ b/vms/platformvm/block/builder/builder.go @@ -533,8 +533,7 @@ func executeTx( mempool.MarkDropped(txID, blockexecutor.ErrConflictingBlockTxs) return false, nil } - err = manager.VerifyUniqueInputs(parentID, txInputs) - if err != nil { + if err := manager.VerifyUniqueInputs(parentID, txInputs); err != nil { txID := tx.ID() mempool.MarkDropped(txID, err) return false, nil From 1bf8709bca076e1e2c93aea199b5699bed42736b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 11:14:38 -0500 Subject: [PATCH 319/400] nit --- vms/platformvm/txs/executor/atomic_tx_executor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index a4535a1d1494..1977608d09c1 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -20,7 +20,7 @@ var _ txs.Visitor = (*atomicTxExecutor)(nil) // modifications. // // This is only used to execute atomic transactions pre-AP5. After AP5 the -// execution was moved to be performed during standard transaction execution. +// execution was moved to [StandardTx]. func AtomicTx( backend *Backend, feeCalculator fee.Calculator, From 018b0aabaa83b7497952bcb9547fe67e5322dbf5 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 11:22:46 -0500 Subject: [PATCH 320/400] Comment --- vms/platformvm/txs/executor/standard_tx_executor.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index b2b6fdc825da..3ccc059bfa8e 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -37,6 +37,17 @@ var ( errMaxNumActiveValidators = errors.New("already at the max number of active validators") ) +// StandardTx executes the standard transaction [tx]. +// +// [state] is modified to represent the state of the chain after the execution +// of [tx]. +// +// Returns: +// - The IDs of any import UTXOs consumed. +// - The, potentially nil, atomic requests that should be performed against +// shared memory when this transaction is accepted. +// - A, potentially nil, function that should be called when this transaction +// is accepted. func StandardTx( backend *Backend, feeCalculator fee.Calculator, From bd6e10b5f2a668c5235f6543f75f62e6758835e9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 11:31:25 -0500 Subject: [PATCH 321/400] Remove P-chain txsmock package --- scripts/mocks.mockgen.source.txt | 1 - .../block/executor/verifier_test.go | 637 +++++++++--------- vms/platformvm/txs/txsmock/unsigned_tx.go | 138 ---- 3 files changed, 301 insertions(+), 475 deletions(-) delete mode 100644 vms/platformvm/txs/txsmock/unsigned_tx.go diff --git a/scripts/mocks.mockgen.source.txt b/scripts/mocks.mockgen.source.txt index 93f4c2fb5127..10cc5a678ebf 100644 --- a/scripts/mocks.mockgen.source.txt +++ b/scripts/mocks.mockgen.source.txt @@ -10,5 +10,4 @@ vms/platformvm/signer/signer.go==Signer=vms/platformvm/signer/signermock/signer. vms/platformvm/state/diff.go==MockDiff=vms/platformvm/state/mock_diff.go vms/platformvm/state/state.go=Chain=MockState=vms/platformvm/state/mock_state.go vms/platformvm/state/state.go=State=MockChain=vms/platformvm/state/mock_chain.go -vms/platformvm/txs/unsigned_tx.go==UnsignedTx=vms/platformvm/txs/txsmock/unsigned_tx.go vms/proposervm/block.go=Block=MockPostForkBlock=vms/proposervm/mock_post_fork_block.go diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 0a5d9e3c662f..9dfc631a4915 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -14,10 +14,13 @@ import ( "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/snowtest" + "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/avalanchego/upgrade/upgradetest" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" @@ -40,7 +43,6 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool/mempoolmock" - "github.com/ava-labs/avalanchego/vms/platformvm/txs/txsmock" "github.com/ava-labs/avalanchego/vms/platformvm/txs/txstest" "github.com/ava-labs/avalanchego/vms/platformvm/utxo" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -49,17 +51,36 @@ import ( validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) -func newTestVerifier(t testing.TB, s state.State) *verifier { +type testVerifierConfig struct { + DB database.Database + Upgrades upgrade.Config + Context *snow.Context +} + +func newTestVerifier(t testing.TB, c testVerifierConfig) *verifier { require := require.New(t) + if c.DB == nil { + c.DB = memdb.New() + } + if c.Upgrades == (upgrade.Config{}) { + c.Upgrades = upgradetest.GetConfig(upgradetest.Latest) + } + if c.Context == nil { + c.Context = snowtest.Context(t, constants.PlatformChainID) + } + mempool, err := mempool.New("", prometheus.NewRegistry(), nil) require.NoError(err) var ( - upgrades = upgradetest.GetConfig(upgradetest.Latest) - ctx = snowtest.Context(t, constants.PlatformChainID) - clock = &mockable.Clock{} - fx = &secp256k1fx.Fx{} + state = statetest.New(t, statetest.Config{ + DB: c.DB, + Upgrades: c.Upgrades, + Context: c.Context, + }) + clock = &mockable.Clock{} + fx = &secp256k1fx.Fx{} ) require.NoError(fx.InitializeVM(&secp256k1fx.TestVM{ Clk: *clock, @@ -69,10 +90,10 @@ func newTestVerifier(t testing.TB, s state.State) *verifier { return &verifier{ backend: &backend{ Mempool: mempool, - lastAccepted: s.GetLastAccepted(), + lastAccepted: state.GetLastAccepted(), blkIDToState: make(map[ids.ID]*blockState), - state: s, - ctx: ctx, + state: state, + ctx: c.Context, }, txExecutorBackend: &executor.Backend{ Config: &config.Config{ @@ -81,13 +102,13 @@ func newTestVerifier(t testing.TB, s state.State) *verifier { DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, SybilProtectionEnabled: true, - UpgradeConfig: upgrades, + UpgradeConfig: c.Upgrades, }, - Ctx: ctx, + Ctx: c.Context, Clk: clock, Fx: fx, FlowChecker: utxo.NewVerifier( - ctx, + c.Context, clock, fx, ), @@ -97,271 +118,314 @@ func newTestVerifier(t testing.TB, s state.State) *verifier { } func TestVerifierVisitProposalBlock(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - s := state.NewMockState(ctrl) - mempool := mempoolmock.NewMempool(ctrl) - parentID := ids.GenerateTestID() - parentStatelessBlk := block.NewMockBlock(ctrl) - parentOnAcceptState := state.NewMockDiff(ctrl) - timestamp := time.Now() - // One call for each of onCommitState and onAbortState. - parentOnAcceptState.EXPECT().GetTimestamp().Return(timestamp).Times(2) - parentOnAcceptState.EXPECT().GetFeeState().Return(gas.State{}).Times(2) - parentOnAcceptState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(2) - parentOnAcceptState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(2) - parentOnAcceptState.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(2) - - backend := &backend{ - lastAccepted: parentID, - blkIDToState: map[ids.ID]*blockState{ - parentID: { - statelessBlock: parentStatelessBlk, - onAcceptState: parentOnAcceptState, - }, - }, - Mempool: mempool, - state: s, - ctx: &snow.Context{ - Log: logging.NoLog{}, - }, - } - manager := &manager{ - txExecutorBackend: &executor.Backend{ - Config: &config.Config{ - UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), + var ( + require = require.New(t) + verifier = newTestVerifier(t, testVerifierConfig{ + Upgrades: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), + }) + initialTimestamp = verifier.state.GetTimestamp() + newTimestamp = initialTimestamp.Add(time.Second) + proposalTx = &txs.Tx{ + Unsigned: &txs.AdvanceTimeTx{ + Time: uint64(newTimestamp.Unix()), }, - Clk: &mockable.Clock{}, - }, - backend: backend, - } + } + ) + require.NoError(proposalTx.Initialize(txs.Codec)) - blkTx := txsmock.NewUnsignedTx(ctrl) - blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.ProposalTxExecutor{})).Return(nil).Times(1) + // Build the block that will be executed on top of the last accepted block. + lastAcceptedID := verifier.state.GetLastAccepted() + lastAccepted, err := verifier.state.GetStatelessBlock(lastAcceptedID) + require.NoError(err) - // We can't serialize [blkTx] because it isn't - // registered with the blocks.Codec. - // Serialize this block with a dummy tx - // and replace it after creation with the mock tx. - // TODO allow serialization of mock txs. - apricotBlk, err := block.NewApricotProposalBlock( - parentID, - 2, - &txs.Tx{ - Unsigned: &txs.AdvanceTimeTx{}, - Creds: []verify.Verifiable{}, - }, + proposalBlock, err := block.NewApricotProposalBlock( + lastAcceptedID, + lastAccepted.Height()+1, + proposalTx, ) require.NoError(err) - apricotBlk.Tx.Unsigned = blkTx - // Set expectations for dependencies. - tx := apricotBlk.Txs()[0] - parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) - mempool.EXPECT().Remove([]*txs.Tx{tx}).Times(1) + // Execute the block. + require.NoError(proposalBlock.Visit(verifier)) - // Visit the block - blk := manager.NewBlock(apricotBlk) - require.NoError(blk.Verify(context.Background())) - require.Contains(manager.backend.blkIDToState, apricotBlk.ID()) - gotBlkState := manager.backend.blkIDToState[apricotBlk.ID()] - require.Equal(apricotBlk, gotBlkState.statelessBlock) - require.Equal(timestamp, gotBlkState.timestamp) + // Verify that the block's execution was recorded as expected. + blkID := proposalBlock.ID() + require.Contains(verifier.blkIDToState, blkID) + executedBlockState := verifier.blkIDToState[blkID] + + txID := proposalTx.ID() - // Assert that the expected tx statuses are set. - _, gotStatus, err := gotBlkState.onCommitState.GetTx(tx.ID()) + onCommit := executedBlockState.onCommitState + require.NotNil(onCommit) + acceptedTx, acceptedStatus, err := onCommit.GetTx(txID) require.NoError(err) - require.Equal(status.Committed, gotStatus) + require.Equal(proposalTx, acceptedTx) + require.Equal(status.Committed, acceptedStatus) - _, gotStatus, err = gotBlkState.onAbortState.GetTx(tx.ID()) + onAbort := executedBlockState.onAbortState + require.NotNil(onAbort) + acceptedTx, acceptedStatus, err = onAbort.GetTx(txID) require.NoError(err) - require.Equal(status.Aborted, gotStatus) + require.Equal(proposalTx, acceptedTx) + require.Equal(status.Aborted, acceptedStatus) + + require.Equal( + &blockState{ + proposalBlockState: proposalBlockState{ + onCommitState: onCommit, + onAbortState: onAbort, + }, + statelessBlock: proposalBlock, - // Visiting again should return nil without using dependencies. - require.NoError(blk.Verify(context.Background())) + timestamp: initialTimestamp, + verifiedHeights: set.Of(uint64(0)), + }, + executedBlockState, + ) } func TestVerifierVisitAtomicBlock(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - // Create mocked dependencies. - s := state.NewMockState(ctrl) - mempool := mempoolmock.NewMempool(ctrl) - parentID := ids.GenerateTestID() - parentStatelessBlk := block.NewMockBlock(ctrl) - grandparentID := ids.GenerateTestID() - parentState := state.NewMockDiff(ctrl) - - backend := &backend{ - blkIDToState: map[ids.ID]*blockState{ - parentID: { - statelessBlock: parentStatelessBlk, - onAcceptState: parentState, - }, - }, - Mempool: mempool, - state: s, - ctx: &snow.Context{ - Log: logging.NoLog{}, - }, - } - manager := &manager{ - txExecutorBackend: &executor.Backend{ - Config: &config.Config{ - UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), + var ( + require = require.New(t) + verifier = newTestVerifier(t, testVerifierConfig{ + Upgrades: upgradetest.GetConfig(upgradetest.ApricotPhase4), + }) + wallet = txstest.NewWallet( + t, + verifier.ctx, + verifier.txExecutorBackend.Config, + verifier.state, + secp256k1fx.NewKeychain(genesis.EWOQKey), + nil, // subnetIDs + nil, // chainIDs + ) + exportedOutput = &avax.TransferableOutput{ + Asset: avax.Asset{ID: verifier.ctx.AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: units.NanoAvax, + OutputOwners: secp256k1fx.OutputOwners{}, }, - Clk: &mockable.Clock{}, - }, - backend: backend, - } + } + initialTimestamp = verifier.state.GetTimestamp() + ) - onAccept := state.NewMockDiff(ctrl) - blkTx := txsmock.NewUnsignedTx(ctrl) - inputs := set.Of(ids.GenerateTestID()) - blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.AtomicTxExecutor{})).DoAndReturn( - func(e *executor.AtomicTxExecutor) error { - e.OnAccept = onAccept - e.Inputs = inputs - return nil + // Build the transaction that will be executed. + atomicTx, err := wallet.IssueExportTx( + verifier.ctx.XChainID, + []*avax.TransferableOutput{ + exportedOutput, }, - ).Times(1) + ) + require.NoError(err) - // We can't serialize [blkTx] because it isn't registered with blocks.Codec. - // Serialize this block with a dummy tx and replace it after creation with - // the mock tx. - // TODO allow serialization of mock txs. - apricotBlk, err := block.NewApricotAtomicBlock( - parentID, - 2, - &txs.Tx{ - Unsigned: &txs.AdvanceTimeTx{}, - Creds: []verify.Verifiable{}, - }, + // Build the block that will be executed on top of the last accepted block. + lastAcceptedID := verifier.state.GetLastAccepted() + lastAccepted, err := verifier.state.GetStatelessBlock(lastAcceptedID) + require.NoError(err) + + atomicBlock, err := block.NewApricotAtomicBlock( + lastAcceptedID, + lastAccepted.Height()+1, + atomicTx, ) require.NoError(err) - apricotBlk.Tx.Unsigned = blkTx - // Set expectations for dependencies. - timestamp := time.Now() - parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) - parentStatelessBlk.EXPECT().Parent().Return(grandparentID).Times(1) - mempool.EXPECT().Remove([]*txs.Tx{apricotBlk.Tx}).Times(1) - onAccept.EXPECT().AddTx(apricotBlk.Tx, status.Committed).Times(1) - onAccept.EXPECT().GetTimestamp().Return(timestamp).Times(1) + // Execute the block. + require.NoError(atomicBlock.Visit(verifier)) - blk := manager.NewBlock(apricotBlk) - require.NoError(blk.Verify(context.Background())) + // Verify that the block's execution was recorded as expected. + blkID := atomicBlock.ID() + require.Contains(verifier.blkIDToState, blkID) + atomicBlockState := verifier.blkIDToState[blkID] + onAccept := atomicBlockState.onAcceptState + require.NotNil(onAccept) - require.Contains(manager.backend.blkIDToState, apricotBlk.ID()) - gotBlkState := manager.backend.blkIDToState[apricotBlk.ID()] - require.Equal(apricotBlk, gotBlkState.statelessBlock) - require.Equal(onAccept, gotBlkState.onAcceptState) - require.Equal(inputs, gotBlkState.inputs) - require.Equal(timestamp, gotBlkState.timestamp) + txID := atomicTx.ID() + acceptedTx, acceptedStatus, err := onAccept.GetTx(txID) + require.NoError(err) + require.Equal(atomicTx, acceptedTx) + require.Equal(status.Committed, acceptedStatus) - // Visiting again should return nil without using dependencies. - require.NoError(blk.Verify(context.Background())) + exportedUTXO := &avax.UTXO{ + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: uint32(len(atomicTx.UTXOs())), + }, + Asset: exportedOutput.Asset, + Out: exportedOutput.Out, + } + exportedUTXOID := exportedUTXO.InputID() + exportedUTXOBytes, err := txs.Codec.Marshal(txs.CodecVersion, exportedUTXO) + require.NoError(err) + + require.Equal( + &blockState{ + statelessBlock: atomicBlock, + + onAcceptState: onAccept, + + timestamp: initialTimestamp, + atomicRequests: map[ids.ID]*atomic.Requests{ + verifier.ctx.XChainID: { + PutRequests: []*atomic.Element{ + { + Key: exportedUTXOID[:], + Value: exportedUTXOBytes, + Traits: [][]byte{}, + }, + }, + }, + }, + verifiedHeights: set.Of(uint64(0)), + }, + atomicBlockState, + ) } func TestVerifierVisitStandardBlock(t *testing.T) { require := require.New(t) - ctrl := gomock.NewController(t) - // Create mocked dependencies. - s := state.NewMockState(ctrl) - mempool := mempoolmock.NewMempool(ctrl) - parentID := ids.GenerateTestID() - parentStatelessBlk := block.NewMockBlock(ctrl) - parentState := state.NewMockDiff(ctrl) + var ( + ctx = snowtest.Context(t, constants.PlatformChainID) - backend := &backend{ - blkIDToState: map[ids.ID]*blockState{ - parentID: { - statelessBlock: parentStatelessBlk, - onAcceptState: parentState, + baseDB = memdb.New() + stateDB = prefixdb.New([]byte{0}, baseDB) + amDB = prefixdb.New([]byte{1}, baseDB) + + am = atomic.NewMemory(amDB) + sm = am.NewSharedMemory(ctx.ChainID) + xChainSM = am.NewSharedMemory(ctx.XChainID) + + owner = secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{genesis.EWOQKey.Address()}, + } + utxo = &avax.UTXO{ + UTXOID: avax.UTXOID{ + TxID: ids.GenerateTestID(), + OutputIndex: 1, }, - }, - Mempool: mempool, - state: s, - ctx: &snow.Context{ - Log: logging.NoLog{}, - }, - } - manager := &manager{ - txExecutorBackend: &executor.Backend{ - Config: &config.Config{ - UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), + Asset: avax.Asset{ID: ctx.AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: units.Avax, + OutputOwners: owner, }, - Clk: &mockable.Clock{}, - }, - backend: backend, - } + } + ) - blkTx := txsmock.NewUnsignedTx(ctrl) - atomicRequests := map[ids.ID]*atomic.Requests{ - ids.GenerateTestID(): { - RemoveRequests: [][]byte{{1}, {2}}, + inputID := utxo.InputID() + utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo) + require.NoError(err) + + require.NoError(xChainSM.Apply(map[ids.ID]*atomic.Requests{ + ctx.ChainID: { PutRequests: []*atomic.Element{ { - Key: []byte{3}, - Value: []byte{4}, - Traits: [][]byte{{5}, {6}}, + Key: inputID[:], + Value: utxoBytes, + Traits: [][]byte{ + genesis.EWOQKey.Address().Bytes(), + }, }, }, }, - } - blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.StandardTxExecutor{})).DoAndReturn( - func(e *executor.StandardTxExecutor) error { - e.OnAccept = func() {} - e.Inputs = set.Set[ids.ID]{} - e.AtomicRequests = atomicRequests - return nil - }, - ).Times(1) - - // We can't serialize [blkTx] because it isn't - // registered with the blocks.Codec. - // Serialize this block with a dummy tx - // and replace it after creation with the mock tx. - // TODO allow serialization of mock txs. - apricotBlk, err := block.NewApricotStandardBlock( - parentID, - 2, /*height*/ - []*txs.Tx{ - { - Unsigned: &txs.AdvanceTimeTx{}, - Creds: []verify.Verifiable{}, - }, - }, + })) + + ctx.SharedMemory = sm + + var ( + verifier = newTestVerifier(t, testVerifierConfig{ + DB: stateDB, + Upgrades: upgradetest.GetConfig(upgradetest.ApricotPhase5), + Context: ctx, + }) + wallet = txstest.NewWallet( + t, + verifier.ctx, + verifier.txExecutorBackend.Config, + verifier.state, + secp256k1fx.NewKeychain(genesis.EWOQKey), + nil, // subnetIDs + []ids.ID{ctx.XChainID}, // Read the UTXO to import + ) + initialTimestamp = verifier.state.GetTimestamp() + ) + + // Build the transaction that will be executed. + tx, err := wallet.IssueImportTx( + verifier.ctx.XChainID, + &owner, ) require.NoError(err) - apricotBlk.Transactions[0].Unsigned = blkTx - // Set expectations for dependencies. - timestamp := time.Now() - parentState.EXPECT().GetTimestamp().Return(timestamp).Times(1) - parentState.EXPECT().GetFeeState().Return(gas.State{}).Times(1) - parentState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).Times(1) - parentState.EXPECT().GetAccruedFees().Return(uint64(0)).Times(1) - parentState.EXPECT().NumActiveSubnetOnlyValidators().Return(0).Times(1) - parentState.EXPECT().GetActiveSubnetOnlyValidatorsIterator().Return(&iterator.Empty[state.SubnetOnlyValidator]{}, nil).Times(1) - parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) - mempool.EXPECT().Remove(apricotBlk.Txs()).Times(1) + // Verify that the transaction is only consuming the imported UTXO. + require.Len(tx.InputIDs(), 1) - blk := manager.NewBlock(apricotBlk) - require.NoError(blk.Verify(context.Background())) + // Build the block that will be executed on top of the last accepted block. + lastAcceptedID := verifier.state.GetLastAccepted() + lastAccepted, err := verifier.state.GetStatelessBlock(lastAcceptedID) + require.NoError(err) - // Assert expected state. - require.Contains(manager.backend.blkIDToState, apricotBlk.ID()) - gotBlkState := manager.backend.blkIDToState[apricotBlk.ID()] - require.Equal(apricotBlk, gotBlkState.statelessBlock) - require.Equal(set.Set[ids.ID]{}, gotBlkState.inputs) - require.Equal(timestamp, gotBlkState.timestamp) + firstBlock, err := block.NewApricotStandardBlock( + lastAcceptedID, + lastAccepted.Height()+1, + []*txs.Tx{tx}, + ) + require.NoError(err) - // Visiting again should return nil without using dependencies. - require.NoError(blk.Verify(context.Background())) + // Execute the block. + require.NoError(firstBlock.Visit(verifier)) + + // Verify that the block's execution was recorded as expected. + firstBlockID := firstBlock.ID() + { + require.Contains(verifier.blkIDToState, firstBlockID) + atomicBlockState := verifier.blkIDToState[firstBlockID] + onAccept := atomicBlockState.onAcceptState + require.NotNil(onAccept) + + txID := tx.ID() + acceptedTx, acceptedStatus, err := onAccept.GetTx(txID) + require.NoError(err) + require.Equal(tx, acceptedTx) + require.Equal(status.Committed, acceptedStatus) + + require.Equal( + &blockState{ + statelessBlock: firstBlock, + + onAcceptState: onAccept, + + inputs: tx.InputIDs(), + timestamp: initialTimestamp, + atomicRequests: map[ids.ID]*atomic.Requests{ + verifier.ctx.XChainID: { + RemoveRequests: [][]byte{ + inputID[:], + }, + }, + }, + verifiedHeights: set.Of(uint64(0)), + }, + atomicBlockState, + ) + } + + // Verify that the import transaction can not be replayed. + { + secondBlock, err := block.NewApricotStandardBlock( + firstBlockID, + firstBlock.Height()+1, + []*txs.Tx{tx}, // Replay the prior transaction + ) + require.NoError(err) + + err = secondBlock.Visit(verifier) + require.ErrorIs(err, errConflictingParentTxs) + + // Verify that the block's execution was not recorded. + require.NotContains(verifier.blkIDToState, secondBlock.ID()) + } } func TestVerifierVisitCommitBlock(t *testing.T) { @@ -737,104 +801,6 @@ func TestBanffCommitBlockTimestampChecks(t *testing.T) { } } -func TestVerifierVisitStandardBlockWithDuplicateInputs(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - // Create mocked dependencies. - s := state.NewMockState(ctrl) - mempool := mempoolmock.NewMempool(ctrl) - - grandParentID := ids.GenerateTestID() - grandParentStatelessBlk := block.NewMockBlock(ctrl) - grandParentState := state.NewMockDiff(ctrl) - parentID := ids.GenerateTestID() - parentStatelessBlk := block.NewMockBlock(ctrl) - parentState := state.NewMockDiff(ctrl) - atomicInputs := set.Of(ids.GenerateTestID()) - - backend := &backend{ - blkIDToState: map[ids.ID]*blockState{ - grandParentID: { - statelessBlock: grandParentStatelessBlk, - onAcceptState: grandParentState, - inputs: atomicInputs, - }, - parentID: { - statelessBlock: parentStatelessBlk, - onAcceptState: parentState, - }, - }, - Mempool: mempool, - state: s, - ctx: &snow.Context{ - Log: logging.NoLog{}, - }, - } - verifier := &verifier{ - txExecutorBackend: &executor.Backend{ - Config: &config.Config{ - UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), - }, - Clk: &mockable.Clock{}, - }, - backend: backend, - } - - blkTx := txsmock.NewUnsignedTx(ctrl) - atomicRequests := map[ids.ID]*atomic.Requests{ - ids.GenerateTestID(): { - RemoveRequests: [][]byte{{1}, {2}}, - PutRequests: []*atomic.Element{ - { - Key: []byte{3}, - Value: []byte{4}, - Traits: [][]byte{{5}, {6}}, - }, - }, - }, - } - blkTx.EXPECT().Visit(gomock.AssignableToTypeOf(&executor.StandardTxExecutor{})).DoAndReturn( - func(e *executor.StandardTxExecutor) error { - e.OnAccept = func() {} - e.Inputs = atomicInputs - e.AtomicRequests = atomicRequests - return nil - }, - ).Times(1) - - // We can't serialize [blkTx] because it isn't - // registered with the blocks.Codec. - // Serialize this block with a dummy tx - // and replace it after creation with the mock tx. - // TODO allow serialization of mock txs. - blk, err := block.NewApricotStandardBlock( - parentID, - 2, - []*txs.Tx{ - { - Unsigned: &txs.AdvanceTimeTx{}, - Creds: []verify.Verifiable{}, - }, - }, - ) - require.NoError(err) - blk.Transactions[0].Unsigned = blkTx - - // Set expectations for dependencies. - timestamp := time.Now() - parentStatelessBlk.EXPECT().Height().Return(uint64(1)).Times(1) - parentState.EXPECT().GetTimestamp().Return(timestamp).Times(1) - parentState.EXPECT().GetFeeState().Return(gas.State{}).Times(1) - parentState.EXPECT().GetSoVExcess().Return(gas.Gas(0)).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) - require.ErrorIs(err, errConflictingParentTxs) -} - func TestVerifierVisitApricotStandardBlockWithProposalBlockParent(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) @@ -1124,13 +1090,12 @@ func TestVerifierVisitBanffAbortBlockUnexpectedParentState(t *testing.T) { } func TestBlockExecutionWithComplexity(t *testing.T) { - s := statetest.New(t, statetest.Config{}) - verifier := newTestVerifier(t, s) + verifier := newTestVerifier(t, testVerifierConfig{}) wallet := txstest.NewWallet( t, verifier.ctx, verifier.txExecutorBackend.Config, - s, + verifier.state, secp256k1fx.NewKeychain(genesis.EWOQKey), nil, // subnetIDs nil, // chainIDs @@ -1185,13 +1150,13 @@ func TestBlockExecutionWithComplexity(t *testing.T) { verifier.txExecutorBackend.Clk.Set(test.timestamp) timestamp, _, err := state.NextBlockTime( verifier.txExecutorBackend.Config.ValidatorFeeConfig, - s, + verifier.state, verifier.txExecutorBackend.Clk, ) require.NoError(err) - lastAcceptedID := s.GetLastAccepted() - lastAccepted, err := s.GetStatelessBlock(lastAcceptedID) + lastAcceptedID := verifier.state.GetLastAccepted() + lastAccepted, err := verifier.state.GetStatelessBlock(lastAcceptedID) require.NoError(err) blk, err := block.NewBanffStandardBlock( diff --git a/vms/platformvm/txs/txsmock/unsigned_tx.go b/vms/platformvm/txs/txsmock/unsigned_tx.go deleted file mode 100644 index a7ba0daac896..000000000000 --- a/vms/platformvm/txs/txsmock/unsigned_tx.go +++ /dev/null @@ -1,138 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: vms/platformvm/txs/unsigned_tx.go -// -// Generated by this command: -// -// mockgen -source=vms/platformvm/txs/unsigned_tx.go -destination=vms/platformvm/txs/txsmock/unsigned_tx.go -package=txsmock -exclude_interfaces= -mock_names=UnsignedTx=UnsignedTx -// - -// Package txsmock is a generated GoMock package. -package txsmock - -import ( - reflect "reflect" - - ids "github.com/ava-labs/avalanchego/ids" - snow "github.com/ava-labs/avalanchego/snow" - set "github.com/ava-labs/avalanchego/utils/set" - avax "github.com/ava-labs/avalanchego/vms/components/avax" - txs "github.com/ava-labs/avalanchego/vms/platformvm/txs" - gomock "go.uber.org/mock/gomock" -) - -// UnsignedTx is a mock of UnsignedTx interface. -type UnsignedTx struct { - ctrl *gomock.Controller - recorder *UnsignedTxMockRecorder -} - -// UnsignedTxMockRecorder is the mock recorder for UnsignedTx. -type UnsignedTxMockRecorder struct { - mock *UnsignedTx -} - -// NewUnsignedTx creates a new mock instance. -func NewUnsignedTx(ctrl *gomock.Controller) *UnsignedTx { - mock := &UnsignedTx{ctrl: ctrl} - mock.recorder = &UnsignedTxMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *UnsignedTx) EXPECT() *UnsignedTxMockRecorder { - return m.recorder -} - -// Bytes mocks base method. -func (m *UnsignedTx) Bytes() []byte { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Bytes") - ret0, _ := ret[0].([]byte) - return ret0 -} - -// Bytes indicates an expected call of Bytes. -func (mr *UnsignedTxMockRecorder) Bytes() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bytes", reflect.TypeOf((*UnsignedTx)(nil).Bytes)) -} - -// InitCtx mocks base method. -func (m *UnsignedTx) InitCtx(ctx *snow.Context) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "InitCtx", ctx) -} - -// InitCtx indicates an expected call of InitCtx. -func (mr *UnsignedTxMockRecorder) InitCtx(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitCtx", reflect.TypeOf((*UnsignedTx)(nil).InitCtx), ctx) -} - -// InputIDs mocks base method. -func (m *UnsignedTx) InputIDs() set.Set[ids.ID] { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "InputIDs") - ret0, _ := ret[0].(set.Set[ids.ID]) - return ret0 -} - -// InputIDs indicates an expected call of InputIDs. -func (mr *UnsignedTxMockRecorder) InputIDs() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InputIDs", reflect.TypeOf((*UnsignedTx)(nil).InputIDs)) -} - -// Outputs mocks base method. -func (m *UnsignedTx) Outputs() []*avax.TransferableOutput { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Outputs") - ret0, _ := ret[0].([]*avax.TransferableOutput) - return ret0 -} - -// Outputs indicates an expected call of Outputs. -func (mr *UnsignedTxMockRecorder) Outputs() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Outputs", reflect.TypeOf((*UnsignedTx)(nil).Outputs)) -} - -// SetBytes mocks base method. -func (m *UnsignedTx) SetBytes(unsignedBytes []byte) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "SetBytes", unsignedBytes) -} - -// SetBytes indicates an expected call of SetBytes. -func (mr *UnsignedTxMockRecorder) SetBytes(unsignedBytes any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBytes", reflect.TypeOf((*UnsignedTx)(nil).SetBytes), unsignedBytes) -} - -// SyntacticVerify mocks base method. -func (m *UnsignedTx) SyntacticVerify(ctx *snow.Context) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SyntacticVerify", ctx) - ret0, _ := ret[0].(error) - return ret0 -} - -// SyntacticVerify indicates an expected call of SyntacticVerify. -func (mr *UnsignedTxMockRecorder) SyntacticVerify(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyntacticVerify", reflect.TypeOf((*UnsignedTx)(nil).SyntacticVerify), ctx) -} - -// Visit mocks base method. -func (m *UnsignedTx) Visit(visitor txs.Visitor) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Visit", visitor) - ret0, _ := ret[0].(error) - return ret0 -} - -// Visit indicates an expected call of Visit. -func (mr *UnsignedTxMockRecorder) Visit(visitor any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Visit", reflect.TypeOf((*UnsignedTx)(nil).Visit), visitor) -} From 24c7d4baa8881d75fdc4b1e9d2c9177e7a7effbb Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 11:40:13 -0500 Subject: [PATCH 322/400] nit --- vms/platformvm/block/executor/verifier_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 9dfc631a4915..4de3a6c8b2dd 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -178,7 +178,7 @@ func TestVerifierVisitProposalBlock(t *testing.T) { statelessBlock: proposalBlock, timestamp: initialTimestamp, - verifiedHeights: set.Of(uint64(0)), + verifiedHeights: set.Of[uint64](0), }, executedBlockState, ) @@ -276,7 +276,7 @@ func TestVerifierVisitAtomicBlock(t *testing.T) { }, }, }, - verifiedHeights: set.Of(uint64(0)), + verifiedHeights: set.Of[uint64](0), }, atomicBlockState, ) @@ -405,7 +405,7 @@ func TestVerifierVisitStandardBlock(t *testing.T) { }, }, }, - verifiedHeights: set.Of(uint64(0)), + verifiedHeights: set.Of[uint64](0), }, atomicBlockState, ) From 70a53b3768b547976a98578b54290c206018b738 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 11:58:14 -0500 Subject: [PATCH 323/400] reduce diff --- vms/platformvm/metrics/tx_metrics.go | 8 +- .../txs/executor/atomic_tx_executor.go | 6 +- .../txs/executor/standard_tx_executor.go | 322 +++++++++--------- 3 files changed, 168 insertions(+), 168 deletions(-) diff --git a/vms/platformvm/metrics/tx_metrics.go b/vms/platformvm/metrics/tx_metrics.go index 7957cb6ab2e9..fd6c63494f7c 100644 --- a/vms/platformvm/metrics/tx_metrics.go +++ b/vms/platformvm/metrics/tx_metrics.go @@ -132,16 +132,16 @@ func (m *txMetrics) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) er return nil } -func (m *txMetrics) BaseTx(*txs.BaseTx) error { +func (m *txMetrics) ConvertSubnetTx(*txs.ConvertSubnetTx) error { m.numTxs.With(prometheus.Labels{ - txLabel: "base", + txLabel: "convert_subnet", }).Inc() return nil } -func (m *txMetrics) ConvertSubnetTx(*txs.ConvertSubnetTx) error { +func (m *txMetrics) BaseTx(*txs.BaseTx) error { m.numTxs.With(prometheus.Labels{ - txLabel: "convert_subnet", + txLabel: "base", }).Inc() return nil } diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index 1977608d09c1..ea5294c2559c 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -92,15 +92,15 @@ func (*atomicTxExecutor) TransformSubnetTx(*txs.TransformSubnetTx) error { return ErrWrongTxType } -func (*atomicTxExecutor) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { +func (*atomicTxExecutor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { return ErrWrongTxType } -func (*atomicTxExecutor) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { +func (*atomicTxExecutor) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { return ErrWrongTxType } -func (*atomicTxExecutor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { +func (*atomicTxExecutor) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { return ErrWrongTxType } diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 3ccc059bfa8e..3ac1e3f48550 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -88,82 +88,6 @@ func (*standardTxExecutor) RewardValidatorTx(*txs.RewardValidatorTx) error { return ErrWrongTxType } -func (e *standardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { - if tx.Validator.NodeID == ids.EmptyNodeID { - return errEmptyNodeID - } - - if _, err := verifyAddValidatorTx( - e.backend, - e.feeCalculator, - e.state, - e.tx, - tx, - ); err != nil { - return err - } - - if err := e.putStaker(tx); err != nil { - return err - } - - txID := e.tx.ID() - avax.Consume(e.state, tx.Ins) - avax.Produce(e.state, txID, tx.Outs) - - if e.backend.Config.PartialSyncPrimaryNetwork && tx.Validator.NodeID == e.backend.Ctx.NodeID { - e.backend.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", - zap.String("reason", "primary network is not being fully synced"), - zap.Stringer("txID", txID), - zap.String("txType", "addValidator"), - zap.Stringer("nodeID", tx.Validator.NodeID), - ) - } - return nil -} - -func (e *standardTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { - if err := verifyAddSubnetValidatorTx( - e.backend, - e.feeCalculator, - e.state, - e.tx, - tx, - ); err != nil { - return err - } - - if err := e.putStaker(tx); err != nil { - return err - } - - txID := e.tx.ID() - avax.Consume(e.state, tx.Ins) - avax.Produce(e.state, txID, tx.Outs) - return nil -} - -func (e *standardTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { - if _, err := verifyAddDelegatorTx( - e.backend, - e.feeCalculator, - e.state, - e.tx, - tx, - ); err != nil { - return err - } - - if err := e.putStaker(tx); err != nil { - return err - } - - txID := e.tx.ID() - avax.Consume(e.state, tx.Ins) - avax.Produce(e.state, txID, tx.Outs) - return nil -} - func (e *standardTxExecutor) CreateChainTx(tx *txs.CreateChainTx) error { if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { return err @@ -261,6 +185,167 @@ func (e *standardTxExecutor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { return nil } +func (e *standardTxExecutor) ExportTx(tx *txs.ExportTx) error { + if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { + return err + } + + var ( + currentTimestamp = e.state.GetTimestamp() + isDurangoActive = e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) + ) + if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { + return err + } + + outs := make([]*avax.TransferableOutput, len(tx.Outs)+len(tx.ExportedOutputs)) + copy(outs, tx.Outs) + copy(outs[len(tx.Outs):], tx.ExportedOutputs) + + if e.backend.Bootstrapped.Get() { + if err := verify.SameSubnet(context.TODO(), e.backend.Ctx, tx.DestinationChain); 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, + outs, + e.tx.Creds, + map[ids.ID]uint64{ + e.backend.Ctx.AVAXAssetID: fee, + }, + ); err != nil { + return fmt.Errorf("failed verifySpend: %w", err) + } + + txID := e.tx.ID() + + // Consume the UTXOS + avax.Consume(e.state, tx.Ins) + // Produce the UTXOS + avax.Produce(e.state, txID, tx.Outs) + + // Note: We apply atomic requests even if we are not verifying atomic + // requests to ensure the shared state will be correct if we later start + // verifying the requests. + elems := make([]*atomic.Element, len(tx.ExportedOutputs)) + for i, out := range tx.ExportedOutputs { + utxo := &avax.UTXO{ + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: uint32(len(tx.Outs) + i), + }, + Asset: avax.Asset{ID: out.AssetID()}, + Out: out.Out, + } + + utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo) + if err != nil { + return fmt.Errorf("failed to marshal UTXO: %w", err) + } + utxoID := utxo.InputID() + elem := &atomic.Element{ + Key: utxoID[:], + Value: utxoBytes, + } + if out, ok := utxo.Out.(avax.Addressable); ok { + elem.Traits = out.Addresses() + } + + elems[i] = elem + } + e.atomicRequests = map[ids.ID]*atomic.Requests{ + tx.DestinationChain: { + PutRequests: elems, + }, + } + return nil +} + +func (e *standardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { + if tx.Validator.NodeID == ids.EmptyNodeID { + return errEmptyNodeID + } + + if _, err := verifyAddValidatorTx( + e.backend, + e.feeCalculator, + e.state, + e.tx, + tx, + ); err != nil { + return err + } + + if err := e.putStaker(tx); err != nil { + return err + } + + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) + + if e.backend.Config.PartialSyncPrimaryNetwork && tx.Validator.NodeID == e.backend.Ctx.NodeID { + e.backend.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", + zap.String("reason", "primary network is not being fully synced"), + zap.Stringer("txID", txID), + zap.String("txType", "addValidator"), + zap.Stringer("nodeID", tx.Validator.NodeID), + ) + } + return nil +} + +func (e *standardTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { + if err := verifyAddSubnetValidatorTx( + e.backend, + e.feeCalculator, + e.state, + e.tx, + tx, + ); err != nil { + return err + } + + if err := e.putStaker(tx); err != nil { + return err + } + + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) + return nil +} + +func (e *standardTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { + if _, err := verifyAddDelegatorTx( + e.backend, + e.feeCalculator, + e.state, + e.tx, + tx, + ); err != nil { + return err + } + + if err := e.putStaker(tx); err != nil { + return err + } + + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) + return nil +} + func (e *standardTxExecutor) ImportTx(tx *txs.ImportTx) error { if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { return err @@ -352,91 +437,6 @@ func (e *standardTxExecutor) ImportTx(tx *txs.ImportTx) error { return nil } -func (e *standardTxExecutor) ExportTx(tx *txs.ExportTx) error { - if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { - return err - } - - var ( - currentTimestamp = e.state.GetTimestamp() - isDurangoActive = e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) - ) - if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { - return err - } - - outs := make([]*avax.TransferableOutput, len(tx.Outs)+len(tx.ExportedOutputs)) - copy(outs, tx.Outs) - copy(outs[len(tx.Outs):], tx.ExportedOutputs) - - if e.backend.Bootstrapped.Get() { - if err := verify.SameSubnet(context.TODO(), e.backend.Ctx, tx.DestinationChain); 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, - outs, - e.tx.Creds, - map[ids.ID]uint64{ - e.backend.Ctx.AVAXAssetID: fee, - }, - ); err != nil { - return fmt.Errorf("failed verifySpend: %w", err) - } - - txID := e.tx.ID() - - // Consume the UTXOS - avax.Consume(e.state, tx.Ins) - // Produce the UTXOS - avax.Produce(e.state, txID, tx.Outs) - - // Note: We apply atomic requests even if we are not verifying atomic - // requests to ensure the shared state will be correct if we later start - // verifying the requests. - elems := make([]*atomic.Element, len(tx.ExportedOutputs)) - for i, out := range tx.ExportedOutputs { - utxo := &avax.UTXO{ - UTXOID: avax.UTXOID{ - TxID: txID, - OutputIndex: uint32(len(tx.Outs) + i), - }, - Asset: avax.Asset{ID: out.AssetID()}, - Out: out.Out, - } - - utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo) - if err != nil { - return fmt.Errorf("failed to marshal UTXO: %w", err) - } - utxoID := utxo.InputID() - elem := &atomic.Element{ - Key: utxoID[:], - Value: utxoBytes, - } - if out, ok := utxo.Out.(avax.Addressable); ok { - elem.Traits = out.Addresses() - } - - elems[i] = elem - } - e.atomicRequests = map[ids.ID]*atomic.Requests{ - tx.DestinationChain: { - PutRequests: elems, - }, - } - return nil -} - // Verifies a [*txs.RemoveSubnetValidatorTx] and, if it passes, executes it on // [e.State]. For verification rules, see [verifyRemoveSubnetValidatorTx]. This // transaction will result in [tx.NodeID] being removed as a validator of From 26c3549e2aea25f07fac350105fa054971a18750 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 11:59:31 -0500 Subject: [PATCH 324/400] reduce diff --- .../txs/executor/standard_tx_executor.go | 182 +++++++++--------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 3ac1e3f48550..04ba94453756 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -185,6 +185,97 @@ func (e *standardTxExecutor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { return nil } +func (e *standardTxExecutor) ImportTx(tx *txs.ImportTx) error { + if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { + return err + } + + var ( + currentTimestamp = e.state.GetTimestamp() + isDurangoActive = e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) + ) + if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { + return err + } + + e.inputs = set.NewSet[ids.ID](len(tx.ImportedInputs)) + utxoIDs := make([][]byte, len(tx.ImportedInputs)) + for i, in := range tx.ImportedInputs { + utxoID := in.UTXOID.InputID() + + e.inputs.Add(utxoID) + utxoIDs[i] = utxoID[:] + } + + // Skip verification of the shared memory inputs if the other primary + // network chains are not guaranteed to be up-to-date. + if e.backend.Bootstrapped.Get() && !e.backend.Config.PartialSyncPrimaryNetwork { + if err := verify.SameSubnet(context.TODO(), e.backend.Ctx, tx.SourceChain); err != nil { + return err + } + + allUTXOBytes, err := e.backend.Ctx.SharedMemory.Get(tx.SourceChain, utxoIDs) + if err != nil { + return fmt.Errorf("failed to get shared memory: %w", err) + } + + utxos := make([]*avax.UTXO, len(tx.Ins)+len(tx.ImportedInputs)) + for index, input := range tx.Ins { + utxo, err := e.state.GetUTXO(input.InputID()) + if err != nil { + return fmt.Errorf("failed to get UTXO %s: %w", &input.UTXOID, err) + } + utxos[index] = utxo + } + for i, utxoBytes := range allUTXOBytes { + utxo := &avax.UTXO{} + if _, err := txs.Codec.Unmarshal(utxoBytes, utxo); err != nil { + return fmt.Errorf("failed to unmarshal UTXO: %w", err) + } + utxos[i+len(tx.Ins)] = utxo + } + + ins := make([]*avax.TransferableInput, len(tx.Ins)+len(tx.ImportedInputs)) + copy(ins, tx.Ins) + copy(ins[len(tx.Ins):], tx.ImportedInputs) + + // Verify the flowcheck + fee, err := e.feeCalculator.CalculateFee(tx) + if err != nil { + return err + } + if err := e.backend.FlowChecker.VerifySpendUTXOs( + tx, + utxos, + ins, + tx.Outs, + e.tx.Creds, + map[ids.ID]uint64{ + e.backend.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) + + // Note: We apply atomic requests even if we are not verifying atomic + // requests to ensure the shared state will be correct if we later start + // verifying the requests. + e.atomicRequests = map[ids.ID]*atomic.Requests{ + tx.SourceChain: { + RemoveRequests: utxoIDs, + }, + } + return nil +} + func (e *standardTxExecutor) ExportTx(tx *txs.ExportTx) error { if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { return err @@ -346,97 +437,6 @@ func (e *standardTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { return nil } -func (e *standardTxExecutor) ImportTx(tx *txs.ImportTx) error { - if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { - return err - } - - var ( - currentTimestamp = e.state.GetTimestamp() - isDurangoActive = e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) - ) - if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { - return err - } - - e.inputs = set.NewSet[ids.ID](len(tx.ImportedInputs)) - utxoIDs := make([][]byte, len(tx.ImportedInputs)) - for i, in := range tx.ImportedInputs { - utxoID := in.UTXOID.InputID() - - e.inputs.Add(utxoID) - utxoIDs[i] = utxoID[:] - } - - // Skip verification of the shared memory inputs if the other primary - // network chains are not guaranteed to be up-to-date. - if e.backend.Bootstrapped.Get() && !e.backend.Config.PartialSyncPrimaryNetwork { - if err := verify.SameSubnet(context.TODO(), e.backend.Ctx, tx.SourceChain); err != nil { - return err - } - - allUTXOBytes, err := e.backend.Ctx.SharedMemory.Get(tx.SourceChain, utxoIDs) - if err != nil { - return fmt.Errorf("failed to get shared memory: %w", err) - } - - utxos := make([]*avax.UTXO, len(tx.Ins)+len(tx.ImportedInputs)) - for index, input := range tx.Ins { - utxo, err := e.state.GetUTXO(input.InputID()) - if err != nil { - return fmt.Errorf("failed to get UTXO %s: %w", &input.UTXOID, err) - } - utxos[index] = utxo - } - for i, utxoBytes := range allUTXOBytes { - utxo := &avax.UTXO{} - if _, err := txs.Codec.Unmarshal(utxoBytes, utxo); err != nil { - return fmt.Errorf("failed to unmarshal UTXO: %w", err) - } - utxos[i+len(tx.Ins)] = utxo - } - - ins := make([]*avax.TransferableInput, len(tx.Ins)+len(tx.ImportedInputs)) - copy(ins, tx.Ins) - copy(ins[len(tx.Ins):], tx.ImportedInputs) - - // Verify the flowcheck - fee, err := e.feeCalculator.CalculateFee(tx) - if err != nil { - return err - } - if err := e.backend.FlowChecker.VerifySpendUTXOs( - tx, - utxos, - ins, - tx.Outs, - e.tx.Creds, - map[ids.ID]uint64{ - e.backend.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) - - // Note: We apply atomic requests even if we are not verifying atomic - // requests to ensure the shared state will be correct if we later start - // verifying the requests. - e.atomicRequests = map[ids.ID]*atomic.Requests{ - tx.SourceChain: { - RemoveRequests: utxoIDs, - }, - } - return nil -} - // Verifies a [*txs.RemoveSubnetValidatorTx] and, if it passes, executes it on // [e.State]. For verification rules, see [verifyRemoveSubnetValidatorTx]. This // transaction will result in [tx.NodeID] being removed as a validator of From 877242fff2cdc13a4c626364943bdc9e48fa34f3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 12:00:45 -0500 Subject: [PATCH 325/400] reduce diff --- .../txs/executor/standard_tx_executor.go | 244 +++++++++--------- 1 file changed, 122 insertions(+), 122 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 04ba94453756..7c561473a7bd 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -530,128 +530,6 @@ func (e *standardTxExecutor) TransformSubnetTx(tx *txs.TransformSubnetTx) error return nil } -func (e *standardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { - if err := verifyAddPermissionlessValidatorTx( - e.backend, - e.feeCalculator, - e.state, - e.tx, - tx, - ); err != nil { - return err - } - - if err := e.putStaker(tx); err != nil { - return err - } - - txID := e.tx.ID() - avax.Consume(e.state, tx.Ins) - avax.Produce(e.state, txID, tx.Outs) - - if e.backend.Config.PartialSyncPrimaryNetwork && - tx.Subnet == constants.PrimaryNetworkID && - tx.Validator.NodeID == e.backend.Ctx.NodeID { - e.backend.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", - zap.String("reason", "primary network is not being fully synced"), - zap.Stringer("txID", txID), - zap.String("txType", "addPermissionlessValidator"), - zap.Stringer("nodeID", tx.Validator.NodeID), - ) - } - - return nil -} - -func (e *standardTxExecutor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { - if err := verifyAddPermissionlessDelegatorTx( - e.backend, - e.feeCalculator, - e.state, - e.tx, - tx, - ); err != nil { - return err - } - - if err := e.putStaker(tx); err != nil { - return err - } - - txID := e.tx.ID() - avax.Consume(e.state, tx.Ins) - avax.Produce(e.state, txID, tx.Outs) - return nil -} - -// Verifies a [*txs.TransferSubnetOwnershipTx] and, if it passes, executes it on -// [e.State]. For verification rules, see [verifyTransferSubnetOwnershipTx]. -// This transaction will result in the ownership of [tx.Subnet] being transferred -// to [tx.Owner]. -func (e *standardTxExecutor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { - err := verifyTransferSubnetOwnershipTx( - e.backend, - e.feeCalculator, - e.state, - e.tx, - tx, - ) - if err != nil { - return err - } - - e.state.SetSubnetOwner(tx.Subnet, tx.Owner) - - txID := e.tx.ID() - avax.Consume(e.state, tx.Ins) - avax.Produce(e.state, txID, tx.Outs) - return nil -} - -func (e *standardTxExecutor) BaseTx(tx *txs.BaseTx) error { - var ( - currentTimestamp = e.state.GetTimestamp() - upgrades = e.backend.Config.UpgradeConfig - ) - if !upgrades.IsDurangoActivated(currentTimestamp) { - return ErrDurangoUpgradeNotActive - } - - // Verify the tx is well-formed - if err := e.tx.SyntacticVerify(e.backend.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.backend.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) - return nil -} - func (e *standardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { var ( currentTimestamp = e.state.GetTimestamp() @@ -780,6 +658,128 @@ func (e *standardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { return nil } +func (e *standardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { + if err := verifyAddPermissionlessValidatorTx( + e.backend, + e.feeCalculator, + e.state, + e.tx, + tx, + ); err != nil { + return err + } + + if err := e.putStaker(tx); err != nil { + return err + } + + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) + + if e.backend.Config.PartialSyncPrimaryNetwork && + tx.Subnet == constants.PrimaryNetworkID && + tx.Validator.NodeID == e.backend.Ctx.NodeID { + e.backend.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", + zap.String("reason", "primary network is not being fully synced"), + zap.Stringer("txID", txID), + zap.String("txType", "addPermissionlessValidator"), + zap.Stringer("nodeID", tx.Validator.NodeID), + ) + } + + return nil +} + +func (e *standardTxExecutor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { + if err := verifyAddPermissionlessDelegatorTx( + e.backend, + e.feeCalculator, + e.state, + e.tx, + tx, + ); err != nil { + return err + } + + if err := e.putStaker(tx); err != nil { + return err + } + + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) + return nil +} + +// Verifies a [*txs.TransferSubnetOwnershipTx] and, if it passes, executes it on +// [e.State]. For verification rules, see [verifyTransferSubnetOwnershipTx]. +// This transaction will result in the ownership of [tx.Subnet] being transferred +// to [tx.Owner]. +func (e *standardTxExecutor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { + err := verifyTransferSubnetOwnershipTx( + e.backend, + e.feeCalculator, + e.state, + e.tx, + tx, + ) + if err != nil { + return err + } + + e.state.SetSubnetOwner(tx.Subnet, tx.Owner) + + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) + return nil +} + +func (e *standardTxExecutor) BaseTx(tx *txs.BaseTx) error { + var ( + currentTimestamp = e.state.GetTimestamp() + upgrades = e.backend.Config.UpgradeConfig + ) + if !upgrades.IsDurangoActivated(currentTimestamp) { + return ErrDurangoUpgradeNotActive + } + + // Verify the tx is well-formed + if err := e.tx.SyntacticVerify(e.backend.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.backend.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) + return nil +} + // Creates the staker as defined in [stakerTx] and adds it to [e.State]. func (e *standardTxExecutor) putStaker(stakerTx txs.Staker) error { var ( From 5110cfa6a24e04172548682458b7ce03eb759f8f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 12:02:38 -0500 Subject: [PATCH 326/400] reduce diff --- vms/platformvm/txs/fee/complexity.go | 112 +++++++++++++-------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index d7e687e90bd5..c9a33b60ce70 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -440,11 +440,11 @@ type complexityVisitor struct { output gas.Dimensions } -func (*complexityVisitor) AddValidatorTx(*txs.AddValidatorTx) error { +func (*complexityVisitor) AddDelegatorTx(*txs.AddDelegatorTx) error { return ErrUnsupportedTx } -func (*complexityVisitor) AddDelegatorTx(*txs.AddDelegatorTx) error { +func (*complexityVisitor) AddValidatorTx(*txs.AddValidatorTx) error { return ErrUnsupportedTx } @@ -460,6 +460,60 @@ func (*complexityVisitor) TransformSubnetTx(*txs.TransformSubnetTx) error { return ErrUnsupportedTx } +func (c *complexityVisitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { + // TODO: Should we include additional complexity for subnets? + baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) + if err != nil { + return err + } + signerComplexity, err := SignerComplexity(tx.Signer) + if err != nil { + return err + } + outputsComplexity, err := OutputComplexity(tx.StakeOuts...) + if err != nil { + return err + } + validatorOwnerComplexity, err := OwnerComplexity(tx.ValidatorRewardsOwner) + if err != nil { + return err + } + delegatorOwnerComplexity, err := OwnerComplexity(tx.DelegatorRewardsOwner) + if err != nil { + return err + } + c.output, err = IntrinsicAddPermissionlessValidatorTxComplexities.Add( + &baseTxComplexity, + &signerComplexity, + &outputsComplexity, + &validatorOwnerComplexity, + &delegatorOwnerComplexity, + ) + return err +} + +func (c *complexityVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { + // TODO: Should we include additional complexity for subnets? + baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) + if err != nil { + return err + } + ownerComplexity, err := OwnerComplexity(tx.DelegationRewardsOwner) + if err != nil { + return err + } + outputsComplexity, err := OutputComplexity(tx.StakeOuts...) + if err != nil { + return err + } + c.output, err = IntrinsicAddPermissionlessDelegatorTxComplexities.Add( + &baseTxComplexity, + &ownerComplexity, + &outputsComplexity, + ) + return err +} + func (c *complexityVisitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { @@ -578,60 +632,6 @@ func (c *complexityVisitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidato return err } -func (c *complexityVisitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { - // TODO: Should we include additional complexity for subnets? - baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) - if err != nil { - return err - } - signerComplexity, err := SignerComplexity(tx.Signer) - if err != nil { - return err - } - outputsComplexity, err := OutputComplexity(tx.StakeOuts...) - if err != nil { - return err - } - validatorOwnerComplexity, err := OwnerComplexity(tx.ValidatorRewardsOwner) - if err != nil { - return err - } - delegatorOwnerComplexity, err := OwnerComplexity(tx.DelegatorRewardsOwner) - if err != nil { - return err - } - c.output, err = IntrinsicAddPermissionlessValidatorTxComplexities.Add( - &baseTxComplexity, - &signerComplexity, - &outputsComplexity, - &validatorOwnerComplexity, - &delegatorOwnerComplexity, - ) - return err -} - -func (c *complexityVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { - // TODO: Should we include additional complexity for subnets? - baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) - if err != nil { - return err - } - ownerComplexity, err := OwnerComplexity(tx.DelegationRewardsOwner) - if err != nil { - return err - } - outputsComplexity, err := OutputComplexity(tx.StakeOuts...) - if err != nil { - return err - } - c.output, err = IntrinsicAddPermissionlessDelegatorTxComplexities.Add( - &baseTxComplexity, - &ownerComplexity, - &outputsComplexity, - ) - return err -} - func (c *complexityVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { From e435ed7904187f7455e30dbcbd5116c79858db9c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 12:03:07 -0500 Subject: [PATCH 327/400] reduce diff --- vms/platformvm/txs/fee/complexity.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index c9a33b60ce70..104a900e18bc 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -530,6 +530,15 @@ func (c *complexityVisitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) e return err } +func (c *complexityVisitor) BaseTx(tx *txs.BaseTx) error { + baseTxComplexity, err := baseTxComplexity(tx) + if err != nil { + return err + } + c.output, err = IntrinsicBaseTxComplexities.Add(&baseTxComplexity) + return err +} + func (c *complexityVisitor) CreateChainTx(tx *txs.CreateChainTx) error { bandwidth, err := math.Mul(uint64(len(tx.FxIDs)), ids.IDLen) if err != nil { @@ -653,15 +662,6 @@ func (c *complexityVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwne return err } -func (c *complexityVisitor) BaseTx(tx *txs.BaseTx) error { - baseTxComplexity, err := baseTxComplexity(tx) - if err != nil { - return err - } - c.output, err = IntrinsicBaseTxComplexities.Add(&baseTxComplexity) - return err -} - func (c *complexityVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { From 3750b1c4e3d8941f1a359fe7eabd84a267e487b6 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 12:03:22 -0500 Subject: [PATCH 328/400] reduce diff --- vms/platformvm/txs/fee/complexity.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 104a900e18bc..5683ac6b3b6a 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -591,36 +591,36 @@ func (c *complexityVisitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { return err } -func (c *complexityVisitor) ImportTx(tx *txs.ImportTx) error { +func (c *complexityVisitor) ExportTx(tx *txs.ExportTx) error { baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { return err } - // TODO: Should imported inputs be more complex? - inputsComplexity, err := InputComplexity(tx.ImportedInputs...) + // TODO: Should exported outputs be more complex? + outputsComplexity, err := OutputComplexity(tx.ExportedOutputs...) if err != nil { return err } - c.output, err = IntrinsicImportTxComplexities.Add( + c.output, err = IntrinsicExportTxComplexities.Add( &baseTxComplexity, - &inputsComplexity, + &outputsComplexity, ) return err } -func (c *complexityVisitor) ExportTx(tx *txs.ExportTx) error { +func (c *complexityVisitor) ImportTx(tx *txs.ImportTx) error { baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { return err } - // TODO: Should exported outputs be more complex? - outputsComplexity, err := OutputComplexity(tx.ExportedOutputs...) + // TODO: Should imported inputs be more complex? + inputsComplexity, err := InputComplexity(tx.ImportedInputs...) if err != nil { return err } - c.output, err = IntrinsicExportTxComplexities.Add( + c.output, err = IntrinsicImportTxComplexities.Add( &baseTxComplexity, - &outputsComplexity, + &inputsComplexity, ) return err } From 687398e38ab941f5aa5e07d9c74b2b0c97313ff7 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 12:05:16 -0500 Subject: [PATCH 329/400] reduce diff --- vms/platformvm/txs/fee/static_calculator.go | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/vms/platformvm/txs/fee/static_calculator.go b/vms/platformvm/txs/fee/static_calculator.go index 1b97349ce2cb..888ccba8621c 100644 --- a/vms/platformvm/txs/fee/static_calculator.go +++ b/vms/platformvm/txs/fee/static_calculator.go @@ -76,26 +76,21 @@ func (c *staticVisitor) CreateSubnetTx(*txs.CreateSubnetTx) error { return nil } -func (c *staticVisitor) ImportTx(*txs.ImportTx) error { +func (c *staticVisitor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { c.fee = c.config.TxFee return nil } -func (c *staticVisitor) ExportTx(*txs.ExportTx) error { - c.fee = c.config.TxFee +func (c *staticVisitor) TransformSubnetTx(*txs.TransformSubnetTx) error { + c.fee = c.config.TransformSubnetTxFee return nil } -func (c *staticVisitor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { +func (c *staticVisitor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { c.fee = c.config.TxFee return nil } -func (c *staticVisitor) TransformSubnetTx(*txs.TransformSubnetTx) error { - c.fee = c.config.TransformSubnetTxFee - return nil -} - func (c *staticVisitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { if tx.Subnet != constants.PrimaryNetworkID { c.fee = c.config.AddSubnetValidatorFee @@ -114,12 +109,17 @@ func (c *staticVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDe return nil } -func (c *staticVisitor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { +func (c *staticVisitor) BaseTx(*txs.BaseTx) error { c.fee = c.config.TxFee return nil } -func (c *staticVisitor) BaseTx(*txs.BaseTx) error { +func (c *staticVisitor) ImportTx(*txs.ImportTx) error { + c.fee = c.config.TxFee + return nil +} + +func (c *staticVisitor) ExportTx(*txs.ExportTx) error { c.fee = c.config.TxFee return nil } From 5a0850551a22718c2883bb0f9efd84bf6fb45a91 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 12:05:51 -0500 Subject: [PATCH 330/400] reduce diff --- vms/platformvm/txs/visitor.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/vms/platformvm/txs/visitor.go b/vms/platformvm/txs/visitor.go index 21c46476fa5f..142a6115d7af 100644 --- a/vms/platformvm/txs/visitor.go +++ b/vms/platformvm/txs/visitor.go @@ -5,7 +5,6 @@ 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 @@ -15,17 +14,11 @@ 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 - BaseTx(*BaseTx) error - - // Etna Transactions: ConvertSubnetTx(*ConvertSubnetTx) error + BaseTx(*BaseTx) error } From 05d24ca03bc45f6b6aebf208a1e35238687f7b5a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 12:06:49 -0500 Subject: [PATCH 331/400] reduce diff --- wallet/chain/p/signer/visitor.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/wallet/chain/p/signer/visitor.go b/wallet/chain/p/signer/visitor.go index b358e1f5d5ea..47bc4542835a 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -51,6 +51,14 @@ func (*visitor) RewardValidatorTx(*txs.RewardValidatorTx) error { return ErrUnsupportedTxType } +func (s *visitor) BaseTx(tx *txs.BaseTx) error { + txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) + if err != nil { + return err + } + return sign(s.tx, false, txSigners) +} + func (s *visitor) AddValidatorTx(tx *txs.AddValidatorTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { @@ -135,7 +143,7 @@ func (s *visitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error return sign(s.tx, true, txSigners) } -func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { +func (s *visitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err @@ -148,41 +156,33 @@ func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { return sign(s.tx, true, txSigners) } -func (s *visitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { +func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err } - return sign(s.tx, true, txSigners) -} - -func (s *visitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { - txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) + subnetAuthSigners, err := s.getSubnetSigners(tx.Subnet, tx.SubnetAuth) if err != nil { return err } + txSigners = append(txSigners, subnetAuthSigners) return sign(s.tx, true, txSigners) } -func (s *visitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { +func (s *visitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err } - subnetAuthSigners, err := s.getSubnetSigners(tx.Subnet, tx.SubnetAuth) - if err != nil { - return err - } - txSigners = append(txSigners, subnetAuthSigners) return sign(s.tx, true, txSigners) } -func (s *visitor) BaseTx(tx *txs.BaseTx) error { +func (s *visitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err } - return sign(s.tx, false, txSigners) + return sign(s.tx, true, txSigners) } func (s *visitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { From 37bede469a17eb16afd8886ac886d79405a0e1cd Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 12:07:20 -0500 Subject: [PATCH 332/400] reduce diff --- wallet/chain/p/signer/visitor.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/wallet/chain/p/signer/visitor.go b/wallet/chain/p/signer/visitor.go index 47bc4542835a..38d501c908b0 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -156,7 +156,7 @@ func (s *visitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) e return sign(s.tx, true, txSigners) } -func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { +func (s *visitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err @@ -169,15 +169,20 @@ func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { return sign(s.tx, true, txSigners) } -func (s *visitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { +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) + if err != nil { + return err + } + txSigners = append(txSigners, subnetAuthSigners) return sign(s.tx, true, txSigners) } -func (s *visitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { +func (s *visitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err @@ -185,16 +190,11 @@ func (s *visitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegato return sign(s.tx, true, txSigners) } -func (s *visitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { +func (s *visitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err } - subnetAuthSigners, err := s.getSubnetSigners(tx.Subnet, tx.SubnetAuth) - if err != nil { - return err - } - txSigners = append(txSigners, subnetAuthSigners) return sign(s.tx, true, txSigners) } From 3098c0bf7b02d177015c0957cc8997fe582fea6a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 12:08:19 -0500 Subject: [PATCH 333/400] reduce diff --- wallet/chain/p/wallet/backend_visitor.go | 40 ++++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/wallet/chain/p/wallet/backend_visitor.go b/wallet/chain/p/wallet/backend_visitor.go index f2f9e646edf8..8e44ee3b5e2d 100644 --- a/wallet/chain/p/wallet/backend_visitor.go +++ b/wallet/chain/p/wallet/backend_visitor.go @@ -58,6 +58,26 @@ func (b *backendVisitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { return b.baseTx(&tx.BaseTx) } +func (b *backendVisitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { + return b.baseTx(&tx.BaseTx) +} + +func (b *backendVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { + b.b.setSubnetOwner( + tx.Subnet, + tx.Owner, + ) + return b.baseTx(&tx.BaseTx) +} + +func (b *backendVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { + return b.baseTx(&tx.BaseTx) +} + +func (b *backendVisitor) BaseTx(tx *txs.BaseTx) error { + return b.baseTx(tx) +} + func (b *backendVisitor) ImportTx(tx *txs.ImportTx) error { err := b.b.removeUTXOs( b.ctx, @@ -91,10 +111,6 @@ func (b *backendVisitor) ExportTx(tx *txs.ExportTx) error { return b.baseTx(&tx.BaseTx) } -func (b *backendVisitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { - return b.baseTx(&tx.BaseTx) -} - func (b *backendVisitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { return b.baseTx(&tx.BaseTx) } @@ -107,22 +123,6 @@ func (b *backendVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessD return b.baseTx(&tx.BaseTx) } -func (b *backendVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { - b.b.setSubnetOwner( - tx.Subnet, - tx.Owner, - ) - return b.baseTx(&tx.BaseTx) -} - -func (b *backendVisitor) BaseTx(tx *txs.BaseTx) error { - return b.baseTx(tx) -} - -func (b *backendVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { - return b.baseTx(&tx.BaseTx) -} - func (b *backendVisitor) baseTx(tx *txs.BaseTx) error { return b.b.removeUTXOs( b.ctx, From 240f1e8410d207c2bb79ebe9c54f1180ef927f55 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 12:59:13 -0500 Subject: [PATCH 334/400] Standardize P-Chain tx visitor order --- vms/platformvm/metrics/tx_metrics.go | 8 +- .../txs/executor/atomic_tx_executor.go | 6 +- .../txs/executor/standard_tx_executor.go | 368 +++++++++--------- vms/platformvm/txs/fee/complexity.go | 232 +++++------ vms/platformvm/txs/fee/static_calculator.go | 22 +- vms/platformvm/txs/visitor.go | 9 +- wallet/chain/p/signer/visitor.go | 32 +- wallet/chain/p/wallet/backend_visitor.go | 40 +- 8 files changed, 362 insertions(+), 355 deletions(-) diff --git a/vms/platformvm/metrics/tx_metrics.go b/vms/platformvm/metrics/tx_metrics.go index fd6c63494f7c..7957cb6ab2e9 100644 --- a/vms/platformvm/metrics/tx_metrics.go +++ b/vms/platformvm/metrics/tx_metrics.go @@ -132,16 +132,16 @@ func (m *txMetrics) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) er return nil } -func (m *txMetrics) ConvertSubnetTx(*txs.ConvertSubnetTx) error { +func (m *txMetrics) BaseTx(*txs.BaseTx) error { m.numTxs.With(prometheus.Labels{ - txLabel: "convert_subnet", + txLabel: "base", }).Inc() return nil } -func (m *txMetrics) BaseTx(*txs.BaseTx) error { +func (m *txMetrics) ConvertSubnetTx(*txs.ConvertSubnetTx) error { m.numTxs.With(prometheus.Labels{ - txLabel: "base", + txLabel: "convert_subnet", }).Inc() return nil } diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index ea5294c2559c..1977608d09c1 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -92,15 +92,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 } diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 7c561473a7bd..3ccc059bfa8e 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -88,6 +88,82 @@ func (*standardTxExecutor) RewardValidatorTx(*txs.RewardValidatorTx) error { return ErrWrongTxType } +func (e *standardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { + if tx.Validator.NodeID == ids.EmptyNodeID { + return errEmptyNodeID + } + + if _, err := verifyAddValidatorTx( + e.backend, + e.feeCalculator, + e.state, + e.tx, + tx, + ); err != nil { + return err + } + + if err := e.putStaker(tx); err != nil { + return err + } + + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) + + if e.backend.Config.PartialSyncPrimaryNetwork && tx.Validator.NodeID == e.backend.Ctx.NodeID { + e.backend.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", + zap.String("reason", "primary network is not being fully synced"), + zap.Stringer("txID", txID), + zap.String("txType", "addValidator"), + zap.Stringer("nodeID", tx.Validator.NodeID), + ) + } + return nil +} + +func (e *standardTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { + if err := verifyAddSubnetValidatorTx( + e.backend, + e.feeCalculator, + e.state, + e.tx, + tx, + ); err != nil { + return err + } + + if err := e.putStaker(tx); err != nil { + return err + } + + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) + return nil +} + +func (e *standardTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { + if _, err := verifyAddDelegatorTx( + e.backend, + e.feeCalculator, + e.state, + e.tx, + tx, + ); err != nil { + return err + } + + if err := e.putStaker(tx); err != nil { + return err + } + + txID := e.tx.ID() + avax.Consume(e.state, tx.Ins) + avax.Produce(e.state, txID, tx.Outs) + return nil +} + func (e *standardTxExecutor) CreateChainTx(tx *txs.CreateChainTx) error { if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { return err @@ -361,42 +437,101 @@ func (e *standardTxExecutor) ExportTx(tx *txs.ExportTx) error { return nil } -func (e *standardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { - if tx.Validator.NodeID == ids.EmptyNodeID { - return errEmptyNodeID - } - - if _, err := verifyAddValidatorTx( +// Verifies a [*txs.RemoveSubnetValidatorTx] and, if it passes, executes it on +// [e.State]. For verification rules, see [verifyRemoveSubnetValidatorTx]. This +// transaction will result in [tx.NodeID] being removed as a validator of +// [tx.SubnetID]. +// Note: [tx.NodeID] may be either a current or pending validator. +func (e *standardTxExecutor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { + staker, isCurrentValidator, err := verifyRemoveSubnetValidatorTx( e.backend, e.feeCalculator, e.state, e.tx, tx, - ); err != nil { + ) + if err != nil { return err } - if err := e.putStaker(tx); err != nil { - return err + if isCurrentValidator { + e.state.DeleteCurrentValidator(staker) + } else { + e.state.DeletePendingValidator(staker) } + // Invariant: There are no permissioned subnet delegators to remove. + txID := e.tx.ID() avax.Consume(e.state, tx.Ins) avax.Produce(e.state, txID, tx.Outs) - if e.backend.Config.PartialSyncPrimaryNetwork && tx.Validator.NodeID == e.backend.Ctx.NodeID { - e.backend.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", - zap.String("reason", "primary network is not being fully synced"), - zap.Stringer("txID", txID), - zap.String("txType", "addValidator"), - zap.Stringer("nodeID", tx.Validator.NodeID), - ) + return nil +} + +func (e *standardTxExecutor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { + currentTimestamp := e.state.GetTimestamp() + if e.backend.Config.UpgradeConfig.IsEtnaActivated(currentTimestamp) { + return errTransformSubnetTxPostEtna + } + + if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { + return err + } + + isDurangoActive := e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) + if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { + return err + } + + // Note: math.MaxInt32 * time.Second < math.MaxInt64 - so this can never + // overflow. + if time.Duration(tx.MaxStakeDuration)*time.Second > e.backend.Config.MaxStakeDuration { + return errMaxStakeDurationTooLarge + } + + baseTxCreds, err := verifyPoASubnetAuthorization(e.backend, e.state, e.tx, tx.Subnet, tx.SubnetAuth) + if err != nil { + return err + } + + // Verify the flowcheck + fee, err := e.feeCalculator.CalculateFee(tx) + if err != nil { + return err } + totalRewardAmount := tx.MaximumSupply - tx.InitialSupply + if err := e.backend.FlowChecker.VerifySpend( + tx, + e.state, + tx.Ins, + tx.Outs, + baseTxCreds, + // Invariant: [tx.AssetID != e.Ctx.AVAXAssetID]. This prevents the first + // entry in this map literal from being overwritten by the + // second entry. + map[ids.ID]uint64{ + e.backend.Ctx.AVAXAssetID: fee, + tx.AssetID: totalRewardAmount, + }, + ); 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) + // Transform the new subnet in the database + e.state.AddSubnetTransformation(e.tx) + e.state.SetCurrentSupply(tx.Subnet, tx.InitialSupply) return nil } -func (e *standardTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { - if err := verifyAddSubnetValidatorTx( +func (e *standardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { + if err := verifyAddPermissionlessValidatorTx( e.backend, e.feeCalculator, e.state, @@ -413,11 +548,23 @@ func (e *standardTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) txID := e.tx.ID() avax.Consume(e.state, tx.Ins) avax.Produce(e.state, txID, tx.Outs) + + if e.backend.Config.PartialSyncPrimaryNetwork && + tx.Subnet == constants.PrimaryNetworkID && + tx.Validator.NodeID == e.backend.Ctx.NodeID { + e.backend.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", + zap.String("reason", "primary network is not being fully synced"), + zap.Stringer("txID", txID), + zap.String("txType", "addPermissionlessValidator"), + zap.Stringer("nodeID", tx.Validator.NodeID), + ) + } + return nil } -func (e *standardTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { - if _, err := verifyAddDelegatorTx( +func (e *standardTxExecutor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { + if err := verifyAddPermissionlessDelegatorTx( e.backend, e.feeCalculator, e.state, @@ -437,13 +584,12 @@ func (e *standardTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { return nil } -// Verifies a [*txs.RemoveSubnetValidatorTx] and, if it passes, executes it on -// [e.State]. For verification rules, see [verifyRemoveSubnetValidatorTx]. This -// transaction will result in [tx.NodeID] being removed as a validator of -// [tx.SubnetID]. -// Note: [tx.NodeID] may be either a current or pending validator. -func (e *standardTxExecutor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { - staker, isCurrentValidator, err := verifyRemoveSubnetValidatorTx( +// Verifies a [*txs.TransferSubnetOwnershipTx] and, if it passes, executes it on +// [e.State]. For verification rules, see [verifyTransferSubnetOwnershipTx]. +// This transaction will result in the ownership of [tx.Subnet] being transferred +// to [tx.Owner]. +func (e *standardTxExecutor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { + err := verifyTransferSubnetOwnershipTx( e.backend, e.feeCalculator, e.state, @@ -454,44 +600,29 @@ func (e *standardTxExecutor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidat return err } - if isCurrentValidator { - e.state.DeleteCurrentValidator(staker) - } else { - e.state.DeletePendingValidator(staker) - } - - // Invariant: There are no permissioned subnet delegators to remove. + e.state.SetSubnetOwner(tx.Subnet, tx.Owner) txID := e.tx.ID() avax.Consume(e.state, tx.Ins) avax.Produce(e.state, txID, tx.Outs) - return nil } -func (e *standardTxExecutor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { - currentTimestamp := e.state.GetTimestamp() - if e.backend.Config.UpgradeConfig.IsEtnaActivated(currentTimestamp) { - return errTransformSubnetTxPostEtna +func (e *standardTxExecutor) BaseTx(tx *txs.BaseTx) error { + var ( + currentTimestamp = e.state.GetTimestamp() + upgrades = e.backend.Config.UpgradeConfig + ) + if !upgrades.IsDurangoActivated(currentTimestamp) { + return ErrDurangoUpgradeNotActive } + // Verify the tx is well-formed if err := e.tx.SyntacticVerify(e.backend.Ctx); err != nil { return err } - isDurangoActive := e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) - if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { - return err - } - - // Note: math.MaxInt32 * time.Second < math.MaxInt64 - so this can never - // overflow. - if time.Duration(tx.MaxStakeDuration)*time.Second > e.backend.Config.MaxStakeDuration { - return errMaxStakeDurationTooLarge - } - - baseTxCreds, err := verifyPoASubnetAuthorization(e.backend, e.state, e.tx, tx.Subnet, tx.SubnetAuth) - if err != nil { + if err := avax.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil { return err } @@ -500,33 +631,24 @@ func (e *standardTxExecutor) TransformSubnetTx(tx *txs.TransformSubnetTx) error if err != nil { return err } - totalRewardAmount := tx.MaximumSupply - tx.InitialSupply if err := e.backend.FlowChecker.VerifySpend( tx, e.state, tx.Ins, tx.Outs, - baseTxCreds, - // Invariant: [tx.AssetID != e.Ctx.AVAXAssetID]. This prevents the first - // entry in this map literal from being overwritten by the - // second entry. + e.tx.Creds, map[ids.ID]uint64{ e.backend.Ctx.AVAXAssetID: fee, - tx.AssetID: totalRewardAmount, }, ); 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) - // Transform the new subnet in the database - e.state.AddSubnetTransformation(e.tx) - e.state.SetCurrentSupply(tx.Subnet, tx.InitialSupply) return nil } @@ -658,128 +780,6 @@ func (e *standardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { return nil } -func (e *standardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { - if err := verifyAddPermissionlessValidatorTx( - e.backend, - e.feeCalculator, - e.state, - e.tx, - tx, - ); err != nil { - return err - } - - if err := e.putStaker(tx); err != nil { - return err - } - - txID := e.tx.ID() - avax.Consume(e.state, tx.Ins) - avax.Produce(e.state, txID, tx.Outs) - - if e.backend.Config.PartialSyncPrimaryNetwork && - tx.Subnet == constants.PrimaryNetworkID && - tx.Validator.NodeID == e.backend.Ctx.NodeID { - e.backend.Ctx.Log.Warn("verified transaction that would cause this node to become unhealthy", - zap.String("reason", "primary network is not being fully synced"), - zap.Stringer("txID", txID), - zap.String("txType", "addPermissionlessValidator"), - zap.Stringer("nodeID", tx.Validator.NodeID), - ) - } - - return nil -} - -func (e *standardTxExecutor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { - if err := verifyAddPermissionlessDelegatorTx( - e.backend, - e.feeCalculator, - e.state, - e.tx, - tx, - ); err != nil { - return err - } - - if err := e.putStaker(tx); err != nil { - return err - } - - txID := e.tx.ID() - avax.Consume(e.state, tx.Ins) - avax.Produce(e.state, txID, tx.Outs) - return nil -} - -// Verifies a [*txs.TransferSubnetOwnershipTx] and, if it passes, executes it on -// [e.State]. For verification rules, see [verifyTransferSubnetOwnershipTx]. -// This transaction will result in the ownership of [tx.Subnet] being transferred -// to [tx.Owner]. -func (e *standardTxExecutor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { - err := verifyTransferSubnetOwnershipTx( - e.backend, - e.feeCalculator, - e.state, - e.tx, - tx, - ) - if err != nil { - return err - } - - e.state.SetSubnetOwner(tx.Subnet, tx.Owner) - - txID := e.tx.ID() - avax.Consume(e.state, tx.Ins) - avax.Produce(e.state, txID, tx.Outs) - return nil -} - -func (e *standardTxExecutor) BaseTx(tx *txs.BaseTx) error { - var ( - currentTimestamp = e.state.GetTimestamp() - upgrades = e.backend.Config.UpgradeConfig - ) - if !upgrades.IsDurangoActivated(currentTimestamp) { - return ErrDurangoUpgradeNotActive - } - - // Verify the tx is well-formed - if err := e.tx.SyntacticVerify(e.backend.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.backend.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) - return nil -} - // Creates the staker as defined in [stakerTx] and adds it to [e.State]. func (e *standardTxExecutor) putStaker(stakerTx txs.Staker) error { var ( diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 5683ac6b3b6a..e1891a3d51a1 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -83,29 +83,6 @@ const ( var ( _ txs.Visitor = (*complexityVisitor)(nil) - IntrinsicAddPermissionlessValidatorTxComplexities = gas.Dimensions{ - gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + - intrinsicValidatorBandwidth + // validator - ids.IDLen + // subnetID - wrappers.IntLen + // signer typeID - wrappers.IntLen + // num stake outs - wrappers.IntLen + // validator rewards typeID - wrappers.IntLen + // delegator rewards typeID - wrappers.IntLen, // delegation shares - gas.DBRead: 1, - gas.DBWrite: 1, - gas.Compute: 0, - } - IntrinsicAddPermissionlessDelegatorTxComplexities = gas.Dimensions{ - gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + - intrinsicValidatorBandwidth + // validator - ids.IDLen + // subnetID - wrappers.IntLen + // num stake outs - wrappers.IntLen, // delegator rewards typeID - gas.DBRead: 1, - gas.DBWrite: 1, - gas.Compute: 0, - } IntrinsicAddSubnetValidatorTxComplexities = gas.Dimensions{ gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + intrinsicSubnetValidatorBandwidth + // subnetValidator @@ -115,19 +92,6 @@ var ( gas.DBWrite: 1, gas.Compute: 0, } - IntrinsicBaseTxComplexities = gas.Dimensions{ - gas.Bandwidth: codec.VersionSize + // codecVersion - wrappers.IntLen + // typeID - wrappers.IntLen + // networkID - ids.IDLen + // blockchainID - wrappers.IntLen + // number of outputs - wrappers.IntLen + // number of inputs - wrappers.IntLen + // length of memo - wrappers.IntLen, // number of credentials - gas.DBRead: 0, - gas.DBWrite: 0, - gas.Compute: 0, - } IntrinsicCreateChainTxComplexities = gas.Dimensions{ gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + ids.IDLen + // subnetID @@ -148,18 +112,18 @@ var ( gas.DBWrite: 1, gas.Compute: 0, } - IntrinsicExportTxComplexities = gas.Dimensions{ + IntrinsicImportTxComplexities = gas.Dimensions{ gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + - ids.IDLen + // destination chainID - wrappers.IntLen, // num exported outputs + ids.IDLen + // source chainID + wrappers.IntLen, // num importing inputs gas.DBRead: 0, gas.DBWrite: 0, gas.Compute: 0, } - IntrinsicImportTxComplexities = gas.Dimensions{ + IntrinsicExportTxComplexities = gas.Dimensions{ gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + - ids.IDLen + // source chainID - wrappers.IntLen, // num importing inputs + ids.IDLen + // destination chainID + wrappers.IntLen, // num exported outputs gas.DBRead: 0, gas.DBWrite: 0, gas.Compute: 0, @@ -174,6 +138,29 @@ var ( gas.DBWrite: 1, gas.Compute: 0, } + IntrinsicAddPermissionlessValidatorTxComplexities = gas.Dimensions{ + gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + + intrinsicValidatorBandwidth + // validator + ids.IDLen + // subnetID + wrappers.IntLen + // signer typeID + wrappers.IntLen + // num stake outs + wrappers.IntLen + // validator rewards typeID + wrappers.IntLen + // delegator rewards typeID + wrappers.IntLen, // delegation shares + gas.DBRead: 1, + gas.DBWrite: 1, + gas.Compute: 0, + } + IntrinsicAddPermissionlessDelegatorTxComplexities = gas.Dimensions{ + gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + + intrinsicValidatorBandwidth + // validator + ids.IDLen + // subnetID + wrappers.IntLen + // num stake outs + wrappers.IntLen, // delegator rewards typeID + gas.DBRead: 1, + gas.DBWrite: 1, + gas.Compute: 0, + } IntrinsicTransferSubnetOwnershipTxComplexities = gas.Dimensions{ gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + ids.IDLen + // subnetID @@ -184,6 +171,19 @@ var ( gas.DBWrite: 1, gas.Compute: 0, } + IntrinsicBaseTxComplexities = gas.Dimensions{ + gas.Bandwidth: codec.VersionSize + // codecVersion + wrappers.IntLen + // typeID + wrappers.IntLen + // networkID + ids.IDLen + // blockchainID + wrappers.IntLen + // number of outputs + wrappers.IntLen + // number of inputs + wrappers.IntLen + // length of memo + wrappers.IntLen, // number of credentials + gas.DBRead: 0, + gas.DBWrite: 0, + gas.Compute: 0, + } IntrinsicConvertSubnetTxComplexities = gas.Dimensions{ gas.Bandwidth: IntrinsicBaseTxComplexities[gas.Bandwidth] + ids.IDLen + // subnetID @@ -440,11 +440,11 @@ type complexityVisitor struct { output gas.Dimensions } -func (*complexityVisitor) AddDelegatorTx(*txs.AddDelegatorTx) error { +func (*complexityVisitor) AddValidatorTx(*txs.AddValidatorTx) error { return ErrUnsupportedTx } -func (*complexityVisitor) AddValidatorTx(*txs.AddValidatorTx) error { +func (*complexityVisitor) AddDelegatorTx(*txs.AddDelegatorTx) error { return ErrUnsupportedTx } @@ -460,60 +460,6 @@ func (*complexityVisitor) TransformSubnetTx(*txs.TransformSubnetTx) error { return ErrUnsupportedTx } -func (c *complexityVisitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { - // TODO: Should we include additional complexity for subnets? - baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) - if err != nil { - return err - } - signerComplexity, err := SignerComplexity(tx.Signer) - if err != nil { - return err - } - outputsComplexity, err := OutputComplexity(tx.StakeOuts...) - if err != nil { - return err - } - validatorOwnerComplexity, err := OwnerComplexity(tx.ValidatorRewardsOwner) - if err != nil { - return err - } - delegatorOwnerComplexity, err := OwnerComplexity(tx.DelegatorRewardsOwner) - if err != nil { - return err - } - c.output, err = IntrinsicAddPermissionlessValidatorTxComplexities.Add( - &baseTxComplexity, - &signerComplexity, - &outputsComplexity, - &validatorOwnerComplexity, - &delegatorOwnerComplexity, - ) - return err -} - -func (c *complexityVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { - // TODO: Should we include additional complexity for subnets? - baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) - if err != nil { - return err - } - ownerComplexity, err := OwnerComplexity(tx.DelegationRewardsOwner) - if err != nil { - return err - } - outputsComplexity, err := OutputComplexity(tx.StakeOuts...) - if err != nil { - return err - } - c.output, err = IntrinsicAddPermissionlessDelegatorTxComplexities.Add( - &baseTxComplexity, - &ownerComplexity, - &outputsComplexity, - ) - return err -} - func (c *complexityVisitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { @@ -530,15 +476,6 @@ func (c *complexityVisitor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) e return err } -func (c *complexityVisitor) BaseTx(tx *txs.BaseTx) error { - baseTxComplexity, err := baseTxComplexity(tx) - if err != nil { - return err - } - c.output, err = IntrinsicBaseTxComplexities.Add(&baseTxComplexity) - return err -} - func (c *complexityVisitor) CreateChainTx(tx *txs.CreateChainTx) error { bandwidth, err := math.Mul(uint64(len(tx.FxIDs)), ids.IDLen) if err != nil { @@ -591,6 +528,23 @@ func (c *complexityVisitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { return err } +func (c *complexityVisitor) ImportTx(tx *txs.ImportTx) error { + baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) + if err != nil { + return err + } + // TODO: Should imported inputs be more complex? + inputsComplexity, err := InputComplexity(tx.ImportedInputs...) + if err != nil { + return err + } + c.output, err = IntrinsicImportTxComplexities.Add( + &baseTxComplexity, + &inputsComplexity, + ) + return err +} + func (c *complexityVisitor) ExportTx(tx *txs.ExportTx) error { baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { @@ -608,35 +562,72 @@ func (c *complexityVisitor) ExportTx(tx *txs.ExportTx) error { return err } -func (c *complexityVisitor) ImportTx(tx *txs.ImportTx) error { +func (c *complexityVisitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { return err } - // TODO: Should imported inputs be more complex? - inputsComplexity, err := InputComplexity(tx.ImportedInputs...) + authComplexity, err := AuthComplexity(tx.SubnetAuth) if err != nil { return err } - c.output, err = IntrinsicImportTxComplexities.Add( + c.output, err = IntrinsicRemoveSubnetValidatorTxComplexities.Add( &baseTxComplexity, - &inputsComplexity, + &authComplexity, ) return err } -func (c *complexityVisitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { +func (c *complexityVisitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { + // TODO: Should we include additional complexity for subnets? baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { return err } - authComplexity, err := AuthComplexity(tx.SubnetAuth) + signerComplexity, err := SignerComplexity(tx.Signer) if err != nil { return err } - c.output, err = IntrinsicRemoveSubnetValidatorTxComplexities.Add( + outputsComplexity, err := OutputComplexity(tx.StakeOuts...) + if err != nil { + return err + } + validatorOwnerComplexity, err := OwnerComplexity(tx.ValidatorRewardsOwner) + if err != nil { + return err + } + delegatorOwnerComplexity, err := OwnerComplexity(tx.DelegatorRewardsOwner) + if err != nil { + return err + } + c.output, err = IntrinsicAddPermissionlessValidatorTxComplexities.Add( &baseTxComplexity, - &authComplexity, + &signerComplexity, + &outputsComplexity, + &validatorOwnerComplexity, + &delegatorOwnerComplexity, + ) + return err +} + +func (c *complexityVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { + // TODO: Should we include additional complexity for subnets? + baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) + if err != nil { + return err + } + ownerComplexity, err := OwnerComplexity(tx.DelegationRewardsOwner) + if err != nil { + return err + } + outputsComplexity, err := OutputComplexity(tx.StakeOuts...) + if err != nil { + return err + } + c.output, err = IntrinsicAddPermissionlessDelegatorTxComplexities.Add( + &baseTxComplexity, + &ownerComplexity, + &outputsComplexity, ) return err } @@ -662,6 +653,15 @@ func (c *complexityVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwne return err } +func (c *complexityVisitor) BaseTx(tx *txs.BaseTx) error { + baseTxComplexity, err := baseTxComplexity(tx) + if err != nil { + return err + } + c.output, err = IntrinsicBaseTxComplexities.Add(&baseTxComplexity) + return err +} + func (c *complexityVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { baseTxComplexity, err := baseTxComplexity(&tx.BaseTx) if err != nil { diff --git a/vms/platformvm/txs/fee/static_calculator.go b/vms/platformvm/txs/fee/static_calculator.go index 888ccba8621c..1b97349ce2cb 100644 --- a/vms/platformvm/txs/fee/static_calculator.go +++ b/vms/platformvm/txs/fee/static_calculator.go @@ -76,21 +76,26 @@ func (c *staticVisitor) CreateSubnetTx(*txs.CreateSubnetTx) error { return nil } -func (c *staticVisitor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { +func (c *staticVisitor) ImportTx(*txs.ImportTx) error { c.fee = c.config.TxFee return nil } -func (c *staticVisitor) TransformSubnetTx(*txs.TransformSubnetTx) error { - c.fee = c.config.TransformSubnetTxFee +func (c *staticVisitor) ExportTx(*txs.ExportTx) error { + c.fee = c.config.TxFee return nil } -func (c *staticVisitor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { +func (c *staticVisitor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { c.fee = c.config.TxFee return nil } +func (c *staticVisitor) TransformSubnetTx(*txs.TransformSubnetTx) error { + c.fee = c.config.TransformSubnetTxFee + return nil +} + func (c *staticVisitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { if tx.Subnet != constants.PrimaryNetworkID { c.fee = c.config.AddSubnetValidatorFee @@ -109,17 +114,12 @@ func (c *staticVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDe return nil } -func (c *staticVisitor) BaseTx(*txs.BaseTx) error { - c.fee = c.config.TxFee - return nil -} - -func (c *staticVisitor) ImportTx(*txs.ImportTx) error { +func (c *staticVisitor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { c.fee = c.config.TxFee return nil } -func (c *staticVisitor) ExportTx(*txs.ExportTx) error { +func (c *staticVisitor) BaseTx(*txs.BaseTx) error { c.fee = c.config.TxFee return nil } diff --git a/vms/platformvm/txs/visitor.go b/vms/platformvm/txs/visitor.go index 142a6115d7af..21c46476fa5f 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,17 @@ 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 } diff --git a/wallet/chain/p/signer/visitor.go b/wallet/chain/p/signer/visitor.go index 38d501c908b0..b358e1f5d5ea 100644 --- a/wallet/chain/p/signer/visitor.go +++ b/wallet/chain/p/signer/visitor.go @@ -51,14 +51,6 @@ func (*visitor) RewardValidatorTx(*txs.RewardValidatorTx) error { return ErrUnsupportedTxType } -func (s *visitor) BaseTx(tx *txs.BaseTx) error { - txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) - if err != nil { - return err - } - return sign(s.tx, false, txSigners) -} - func (s *visitor) AddValidatorTx(tx *txs.AddValidatorTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { @@ -143,7 +135,7 @@ func (s *visitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error return sign(s.tx, true, txSigners) } -func (s *visitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { +func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err @@ -156,20 +148,23 @@ func (s *visitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) e return sign(s.tx, true, txSigners) } -func (s *visitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { +func (s *visitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err } - subnetAuthSigners, err := s.getSubnetSigners(tx.Subnet, tx.SubnetAuth) + return sign(s.tx, true, txSigners) +} + +func (s *visitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { + txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err } - txSigners = append(txSigners, subnetAuthSigners) return sign(s.tx, true, txSigners) } -func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { +func (s *visitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err @@ -182,19 +177,24 @@ func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { return sign(s.tx, true, txSigners) } -func (s *visitor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error { +func (s *visitor) BaseTx(tx *txs.BaseTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err } - return sign(s.tx, true, txSigners) + return sign(s.tx, false, txSigners) } -func (s *visitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessDelegatorTx) error { +func (s *visitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { txSigners, err := s.getSigners(constants.PlatformChainID, tx.Ins) if err != nil { return err } + subnetAuthSigners, err := s.getSubnetSigners(tx.Subnet, tx.SubnetAuth) + if err != nil { + return err + } + txSigners = append(txSigners, subnetAuthSigners) return sign(s.tx, true, txSigners) } diff --git a/wallet/chain/p/wallet/backend_visitor.go b/wallet/chain/p/wallet/backend_visitor.go index 8e44ee3b5e2d..f2f9e646edf8 100644 --- a/wallet/chain/p/wallet/backend_visitor.go +++ b/wallet/chain/p/wallet/backend_visitor.go @@ -58,26 +58,6 @@ func (b *backendVisitor) CreateSubnetTx(tx *txs.CreateSubnetTx) error { return b.baseTx(&tx.BaseTx) } -func (b *backendVisitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { - return b.baseTx(&tx.BaseTx) -} - -func (b *backendVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { - b.b.setSubnetOwner( - tx.Subnet, - tx.Owner, - ) - return b.baseTx(&tx.BaseTx) -} - -func (b *backendVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { - return b.baseTx(&tx.BaseTx) -} - -func (b *backendVisitor) BaseTx(tx *txs.BaseTx) error { - return b.baseTx(tx) -} - func (b *backendVisitor) ImportTx(tx *txs.ImportTx) error { err := b.b.removeUTXOs( b.ctx, @@ -111,6 +91,10 @@ func (b *backendVisitor) ExportTx(tx *txs.ExportTx) error { return b.baseTx(&tx.BaseTx) } +func (b *backendVisitor) RemoveSubnetValidatorTx(tx *txs.RemoveSubnetValidatorTx) error { + return b.baseTx(&tx.BaseTx) +} + func (b *backendVisitor) TransformSubnetTx(tx *txs.TransformSubnetTx) error { return b.baseTx(&tx.BaseTx) } @@ -123,6 +107,22 @@ func (b *backendVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessD return b.baseTx(&tx.BaseTx) } +func (b *backendVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { + b.b.setSubnetOwner( + tx.Subnet, + tx.Owner, + ) + return b.baseTx(&tx.BaseTx) +} + +func (b *backendVisitor) BaseTx(tx *txs.BaseTx) error { + return b.baseTx(tx) +} + +func (b *backendVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { + return b.baseTx(&tx.BaseTx) +} + func (b *backendVisitor) baseTx(tx *txs.BaseTx) error { return b.b.removeUTXOs( b.ctx, From efa35a4275eb04220a625d9b522f6d96c7b41a10 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 13:28:31 -0500 Subject: [PATCH 335/400] nit --- wallet/chain/p/builder/builder.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index 80936c30fed6..e2b95c185b13 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -889,13 +889,13 @@ func (b *builder) NewRegisterSubnetValidatorTx( b.context.AVAXAssetID: balance, } toStake = map[ids.ID]uint64{} - ) - ops := common.NewOptions(options) - memo := ops.Memo() - memoComplexity := gas.Dimensions{ - gas.Bandwidth: uint64(len(memo)), - } + 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 From 031b989b8e7b68280d8bd3904037064289a328f1 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 13:31:39 -0500 Subject: [PATCH 336/400] nit --- wallet/chain/p/builder/builder.go | 2 +- wallet/chain/p/wallet/wallet.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index e2b95c185b13..8a429fbe24c3 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -165,7 +165,7 @@ type Builder interface { options ...common.Option, ) (*txs.ConvertSubnetTx, error) - // RegisterSubnetValidatorTx adds a validator to a Permissionless L1. + // RegisterSubnetValidatorTx adds a validator to an L1. // // - [balance] that the validator should allocate to continuous fees // - [proofOfPossession] is the BLS PoP for the key included in the Warp diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index 21b60abd5d4f..fd6fd9a782e0 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -152,7 +152,7 @@ type Wallet interface { ) (*txs.Tx, error) // IssueRegisterSubnetValidatorTx creates, signs, and issues a transaction - // that adds a validator to a Permissionless L1. + // that adds a validator to an L1. // // - [balance] that the validator should allocate to continuous fees // - [proofOfPossession] is the BLS PoP for the key included in the Warp From a3f61865a13f70808a70c0593a4752a5e895cf19 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 13:53:29 -0500 Subject: [PATCH 337/400] Add tx builder test --- wallet/chain/p/builder_test.go | 102 +++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/wallet/chain/p/builder_test.go b/wallet/chain/p/builder_test.go index f56a0eb0896b..bc03f30814e9 100644 --- a/wallet/chain/p/builder_test.go +++ b/wallet/chain/p/builder_test.go @@ -26,7 +26,9 @@ 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" "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/vms/types" "github.com/ava-labs/avalanchego/wallet/chain/p/builder" @@ -747,6 +749,106 @@ func TestConvertSubnetTx(t *testing.T) { } } +func TestRegisterSubnetValidatorTx(t *testing.T) { + const ( + expiry = 1731005097 + weight = 7905001371 + + balance = units.Avax + ) + + sk, err := bls.NewSecretKey() + require.NoError(t, err) + pop := signer.NewProofOfPossession(sk) + + addressedCallPayload, err := message.NewRegisterSubnetValidator( + subnetID, + nodeID, + pop.PublicKey, + expiry, + message.PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + message.PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + weight, + ) + require.NoError(t, err) + + addressedCall, err := payload.NewAddressedCall( + utils.RandomBytes(20), + addressedCallPayload.Bytes(), + ) + require.NoError(t, err) + + unsignedWarp, err := warp.NewUnsignedMessage( + constants.UnitTestID, + ids.GenerateTestID(), + addressedCall.Bytes(), + ) + require.NoError(t, err) + + signers := set.NewBits(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{ + Signers: signers.Bytes(), + Signature: sigBytes, + }, + ) + require.NoError(t, err) + warpMessageBytes := warp.Bytes() + + for _, e := range testEnvironmentPostEtna { + t.Run(e.name, func(t *testing.T) { + var ( + require = require.New(t) + chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*avax.UTXO{ + constants.PlatformChainID: utxos, + }) + backend = wallet.NewBackend(e.context, chainUTXOs, nil) + builder = builder.New(set.Of(utxoAddr), e.context, backend) + ) + + utx, err := builder.NewRegisterSubnetValidatorTx( + balance, + pop.ProofOfPossession, + warpMessageBytes, + common.WithMemo(e.memo), + ) + require.NoError(err) + require.Equal(balance, utx.Balance) + require.Equal(pop.ProofOfPossession, utx.ProofOfPossession) + require.Equal(types.JSONByteSlice(warpMessageBytes), utx.Message) + require.Equal(types.JSONByteSlice(e.memo), utx.Memo) + requireFeeIsCorrect( + require, + e.feeCalculator, + utx, + &utx.BaseTx.BaseTx, + nil, + nil, + map[ids.ID]uint64{ + e.context.AVAXAssetID: balance, // Balance of the validator + }, + ) + }) + } +} + func makeTestUTXOs(utxosKey *secp256k1.PrivateKey) []*avax.UTXO { // Note: we avoid ids.GenerateTestNodeID here to make sure that UTXO IDs // won't change run by run. This simplifies checking what utxos are included From f7678005a7961c890cbd12ab4a8f00fbc66bced0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 14:16:22 -0500 Subject: [PATCH 338/400] add comments --- vms/platformvm/validators/manager.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/vms/platformvm/validators/manager.go b/vms/platformvm/validators/manager.go index b90294206f98..6e620bdc7c70 100644 --- a/vms/platformvm/validators/manager.go +++ b/vms/platformvm/validators/manager.go @@ -26,9 +26,17 @@ import ( ) const ( + // MaxRecentlyAcceptedWindowSize is the maximum number of blocks that the + // recommended minimum height will lag behind the last accepted block. MaxRecentlyAcceptedWindowSize = 64 + // MinRecentlyAcceptedWindowSize is the minimum number of blocks that the + // recommended minimum height will lag behind the last accepted block. MinRecentlyAcceptedWindowSize = 0 - RecentlyAcceptedWindowTTL = 30 * time.Second + // RecentlyAcceptedWindowTTL is the amount of time after a block is accepted + // to avoid recommending it as the minimum height. If the evicting, or not + // evicting, the block would violate either the maxiumum or minimum window + // size, the block will not be evicted. + RecentlyAcceptedWindowTTL = 30 * time.Second validatorSetsCacheSize = 64 ) From e64ca232caebf64a157ae3c1d3b675aa25cd898e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 15:01:02 -0500 Subject: [PATCH 339/400] Add RegisterSubnetValidatorTx serialization and SyntacticVerify tests --- .../txs/register_subnet_validator_tx_test.go | 393 ++++++++++++++++++ .../register_subnet_validator_tx_test.json | 175 ++++++++ 2 files changed, 568 insertions(+) create mode 100644 vms/platformvm/txs/register_subnet_validator_tx_test.go create mode 100644 vms/platformvm/txs/register_subnet_validator_tx_test.json diff --git a/vms/platformvm/txs/register_subnet_validator_tx_test.go b/vms/platformvm/txs/register_subnet_validator_tx_test.go new file mode 100644 index 000000000000..64055b6171e0 --- /dev/null +++ b/vms/platformvm/txs/register_subnet_validator_tx_test.go @@ -0,0 +1,393 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txs + +import ( + "encoding/hex" + "encoding/json" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + _ "embed" + + "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" +) + +//go:embed register_subnet_validator_tx_test.json +var registerSubnetValidatorTxJSON []byte + +func TestRegisterSubnetValidatorTxSerialization(t *testing.T) { + require := require.New(t) + + const balance = units.Avax + + skBytes, err := hex.DecodeString("6668fecd4595b81e4d568398c820bbf3f073cb222902279fa55ebb84764ed2e3") + require.NoError(err) + sk, err := bls.SecretKeyFromBytes(skBytes) + require.NoError(err) + + var ( + pop = signer.NewProofOfPossession(sk) + message = []byte("message") + addr = ids.ShortID{ + 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0x44, 0x55, 0x66, 0x77, + } + avaxAssetID = ids.ID{ + 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, + 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, + 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, + 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, + } + customAssetID = ids.ID{ + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + } + txID = ids.ID{ + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + } + ) + + var unsignedTx UnsignedTx = &RegisterSubnetValidatorTx{ + BaseTx: BaseTx{ + BaseTx: avax.BaseTx{ + NetworkID: constants.UnitTestID, + BlockchainID: constants.PlatformChainID, + Outs: []*avax.TransferableOutput{ + { + Asset: avax.Asset{ + ID: avaxAssetID, + }, + Out: &stakeable.LockOut{ + Locktime: 87654321, + TransferableOut: &secp256k1fx.TransferOutput{ + Amt: 1, + OutputOwners: secp256k1fx.OutputOwners{ + Locktime: 12345678, + Threshold: 0, + Addrs: []ids.ShortID{}, + }, + }, + }, + }, + { + Asset: avax.Asset{ + ID: customAssetID, + }, + Out: &stakeable.LockOut{ + Locktime: 876543210, + TransferableOut: &secp256k1fx.TransferOutput{ + Amt: 0xffffffffffffffff, + OutputOwners: secp256k1fx.OutputOwners{ + Locktime: 0, + Threshold: 1, + Addrs: []ids.ShortID{ + addr, + }, + }, + }, + }, + }, + }, + Ins: []*avax.TransferableInput{ + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: 1, + }, + Asset: avax.Asset{ + ID: avaxAssetID, + }, + In: &secp256k1fx.TransferInput{ + Amt: units.Avax, + Input: secp256k1fx.Input{ + SigIndices: []uint32{2, 5}, + }, + }, + }, + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: 2, + }, + Asset: avax.Asset{ + ID: customAssetID, + }, + In: &stakeable.LockIn{ + Locktime: 876543210, + TransferableIn: &secp256k1fx.TransferInput{ + Amt: 0xefffffffffffffff, + Input: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + }, + }, + }, + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: 3, + }, + Asset: avax.Asset{ + ID: customAssetID, + }, + In: &secp256k1fx.TransferInput{ + Amt: 0x1000000000000000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{}, + }, + }, + }, + }, + Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), + }, + }, + Balance: balance, + ProofOfPossession: pop.ProofOfPossession, + Message: message, + } + txBytes, err := Codec.Marshal(CodecVersion, &unsignedTx) + require.NoError(err) + + expectedBytes := []byte{ + // Codec version + 0x00, 0x00, + // RegisterSubnetValidatorTx Type ID + 0x00, 0x00, 0x00, 0x24, + // Network ID + 0x00, 0x00, 0x00, 0x0a, + // P-chain blockchain ID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Number of outputs + 0x00, 0x00, 0x00, 0x02, + // Outputs[0] + // AVAX assetID + 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, + 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, + 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, + 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, + // Stakeable locked output type ID + 0x00, 0x00, 0x00, 0x16, + // Locktime + 0x00, 0x00, 0x00, 0x00, 0x05, 0x39, 0x7f, 0xb1, + // secp256k1fx transfer output type ID + 0x00, 0x00, 0x00, 0x07, + // amount + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + // secp256k1fx output locktime + 0x00, 0x00, 0x00, 0x00, 0x00, 0xbc, 0x61, 0x4e, + // threshold + 0x00, 0x00, 0x00, 0x00, + // number of addresses + 0x00, 0x00, 0x00, 0x00, + // Outputs[1] + // custom asset ID + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + // Stakeable locked output type ID + 0x00, 0x00, 0x00, 0x16, + // Locktime + 0x00, 0x00, 0x00, 0x00, 0x34, 0x3e, 0xfc, 0xea, + // secp256k1fx transfer output type ID + 0x00, 0x00, 0x00, 0x07, + // amount + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + // secp256k1fx output locktime + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // threshold + 0x00, 0x00, 0x00, 0x01, + // number of addresses + 0x00, 0x00, 0x00, 0x01, + // address[0] + 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0x44, 0x55, 0x66, 0x77, + // number of inputs + 0x00, 0x00, 0x00, 0x03, + // inputs[0] + // TxID + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + // Tx output index + 0x00, 0x00, 0x00, 0x01, + // AVAX assetID + 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, + 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, + 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, + 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, + // secp256k1fx transfer input type ID + 0x00, 0x00, 0x00, 0x05, + // input amount = 1 Avax + 0x00, 0x00, 0x00, 0x00, 0x3b, 0x9a, 0xca, 0x00, + // number of signatures needed in input + 0x00, 0x00, 0x00, 0x02, + // index of first signer + 0x00, 0x00, 0x00, 0x02, + // index of second signer + 0x00, 0x00, 0x00, 0x05, + // inputs[1] + // TxID + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + // Tx output index + 0x00, 0x00, 0x00, 0x02, + // Custom asset ID + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + // Stakeable locked input type ID + 0x00, 0x00, 0x00, 0x15, + // Locktime + 0x00, 0x00, 0x00, 0x00, 0x34, 0x3e, 0xfc, 0xea, + // secp256k1fx transfer input type ID + 0x00, 0x00, 0x00, 0x05, + // input amount + 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + // number of signatures needed in input + 0x00, 0x00, 0x00, 0x01, + // index of signer + 0x00, 0x00, 0x00, 0x00, + // inputs[2] + // TxID + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + // Tx output index + 0x00, 0x00, 0x00, 0x03, + // custom asset ID + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + // secp256k1fx transfer input type ID + 0x00, 0x00, 0x00, 0x05, + // input amount + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // number of signatures needed in input + 0x00, 0x00, 0x00, 0x00, + // length of memo + 0x00, 0x00, 0x00, 0x14, + // memo + 0xf0, 0x9f, 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, + 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, + 0x01, 0x23, 0x45, 0x21, + // balance + 0x00, 0x00, 0x00, 0x00, 0x3b, 0x9a, 0xca, 0x00, + // proof of possession + 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, + // length of message + 0x00, 0x00, 0x00, 0x07, + // message + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + } + require.Equal(expectedBytes, txBytes) + + ctx := snowtest.Context(t, constants.PlatformChainID) + unsignedTx.InitCtx(ctx) + + txJSON, err := json.MarshalIndent(unsignedTx, "", "\t") + require.NoError(err) + require.Equal( + // Normalize newlines for Windows + strings.ReplaceAll(string(registerSubnetValidatorTxJSON), "\r\n", "\n"), + string(txJSON), + ) +} + +func TestRegisterSubnetValidatorTxSyntacticVerify(t *testing.T) { + ctx := snowtest.Context(t, ids.GenerateTestID()) + tests := []struct { + name string + tx *RegisterSubnetValidatorTx + expectedErr error + }{ + { + name: "nil tx", + tx: nil, + expectedErr: ErrNilTx, + }, + { + name: "already verified", + // The tx includes invalid data to verify that a cached result is + // returned. + tx: &RegisterSubnetValidatorTx{ + BaseTx: BaseTx{ + SyntacticallyVerified: true, + }, + }, + expectedErr: nil, + }, + { + name: "invalid BaseTx", + tx: &RegisterSubnetValidatorTx{ + BaseTx: BaseTx{}, + }, + expectedErr: avax.ErrWrongNetworkID, + }, + { + name: "passes verification", + tx: &RegisterSubnetValidatorTx{ + BaseTx: BaseTx{ + BaseTx: avax.BaseTx{ + NetworkID: ctx.NetworkID, + BlockchainID: ctx.ChainID, + }, + }, + }, + expectedErr: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + err := test.tx.SyntacticVerify(ctx) + require.ErrorIs(err, test.expectedErr) + if test.expectedErr != nil { + return + } + require.True(test.tx.SyntacticallyVerified) + }) + } +} diff --git a/vms/platformvm/txs/register_subnet_validator_tx_test.json b/vms/platformvm/txs/register_subnet_validator_tx_test.json new file mode 100644 index 000000000000..f741e2fdbb37 --- /dev/null +++ b/vms/platformvm/txs/register_subnet_validator_tx_test.json @@ -0,0 +1,175 @@ +{ + "networkID": 10, + "blockchainID": "11111111111111111111111111111111LpoYY", + "outputs": [ + { + "assetID": "FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "locktime": 87654321, + "output": { + "addresses": [], + "amount": 1, + "locktime": 12345678, + "threshold": 0 + } + } + }, + { + "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "locktime": 876543210, + "output": { + "addresses": [ + "P-testing1g32kvaugnx4tk3z4vemc3xd2hdz92enhgrdu9n" + ], + "amount": 18446744073709551615, + "locktime": 0, + "threshold": 1 + } + } + } + ], + "inputs": [ + { + "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", + "outputIndex": 1, + "assetID": "FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "amount": 1000000000, + "signatureIndices": [ + 2, + 5 + ] + } + }, + { + "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", + "outputIndex": 2, + "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "locktime": 876543210, + "input": { + "amount": 17293822569102704639, + "signatureIndices": [ + 0 + ] + } + } + }, + { + "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", + "outputIndex": 3, + "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "amount": 1152921504606846976, + "signatureIndices": [] + } + } + ], + "memo": "0xf09f98850a77656c6c2074686174277301234521", + "balance": 1000000000, + "proofOfPossession": [ + 140, + 253, + 121, + 9, + 209, + 83, + 185, + 96, + 75, + 98, + 177, + 67, + 186, + 54, + 32, + 123, + 183, + 230, + 72, + 103, + 66, + 68, + 128, + 32, + 42, + 103, + 220, + 104, + 118, + 131, + 70, + 217, + 92, + 144, + 152, + 60, + 45, + 39, + 156, + 100, + 196, + 60, + 81, + 19, + 107, + 42, + 5, + 224, + 22, + 2, + 213, + 42, + 166, + 55, + 111, + 218, + 23, + 250, + 110, + 42, + 24, + 160, + 131, + 228, + 157, + 156, + 69, + 14, + 171, + 123, + 137, + 177, + 213, + 85, + 93, + 165, + 196, + 137, + 135, + 46, + 2, + 183, + 229, + 34, + 123, + 119, + 85, + 10, + 241, + 51, + 14, + 90, + 113, + 248, + 195, + 104 + ], + "message": "0x6d657373616765" +} \ No newline at end of file From 4c866494af08207bc31cbf9419c87eb7ab654914 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 15:12:41 -0500 Subject: [PATCH 340/400] add TODO --- vms/platformvm/txs/executor/standard_tx_executor.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 404d6bd84943..b9331b116bfe 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -30,6 +30,8 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" ) +// TODO: Before Etna, ensure that the maximum number of expiries to track is +// limited to a reasonable number by this window. const ( second = 1 minute = 60 * second From 987c4e2871672afed7c933d7f9c3255fe92fd574 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 15:26:09 -0500 Subject: [PATCH 341/400] reduce diff --- vms/platformvm/txs/executor/standard_tx_executor.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index b9331b116bfe..78cbe4f307c0 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -838,9 +838,6 @@ func (e *standardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal if err != nil { return err } - if warpMessage.NetworkID != e.backend.Ctx.NetworkID { - return fmt.Errorf("expected networkID %d but got %d", e.backend.Ctx.NetworkID, warpMessage.NetworkID) - } addressedCall, err := payload.ParseAddressedCall(warpMessage.Payload) if err != nil { From 98ffb01a75005627a961aab50157c897e6502a8c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 15:38:52 -0500 Subject: [PATCH 342/400] remove dead code --- vms/platformvm/txs/executor/standard_tx_executor.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 78cbe4f307c0..ab60bd03dc7d 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -867,15 +867,8 @@ 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 not to be after %d but got %d", maxAllowedExpiry, msg.Expiry) + if secondsUntilExpiry := msg.Expiry - currentTimestampUnix; secondsUntilExpiry > RegisterSubnetValidatorTxExpiryWindow { + return fmt.Errorf("expected expiry not to be more than %d seconds in the future but got %d", RegisterSubnetValidatorTxExpiryWindow, secondsUntilExpiry) } pop := signer.ProofOfPossession{ From 6b23e82bba4c42661417cf7cdd9e446efc5d966e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 15:51:01 -0500 Subject: [PATCH 343/400] Cleanup code --- .../txs/executor/standard_tx_executor.go | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index ab60bd03dc7d..3563666b054d 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -834,16 +834,15 @@ func (e *standardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return err } + // Parse the warp message. warpMessage, err := warp.ParseMessage(tx.Message) if err != nil { return err } - addressedCall, err := payload.ParseAddressedCall(warpMessage.Payload) if err != nil { return err } - msg, err := message.ParseRegisterSubnetValidator(addressedCall.Payload) if err != nil { return err @@ -852,6 +851,8 @@ func (e *standardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return err } + // Verify that the warp message was sent from the expected chain and + // address. subnetConversion, err := e.state.GetSubnetConversion(msg.SubnetID) if err != nil { return err @@ -863,6 +864,7 @@ func (e *standardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return fmt.Errorf("expected address %s but got %s", subnetConversion.Addr, addressedCall.SourceAddress) } + // Verify that the message contains a valid expiry time. currentTimestampUnix := uint64(currentTimestamp.Unix()) if msg.Expiry <= currentTimestampUnix { return fmt.Errorf("expected expiry to be after %d but got %d", currentTimestampUnix, msg.Expiry) @@ -871,14 +873,7 @@ func (e *standardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return fmt.Errorf("expected expiry not to be more than %d seconds in the future but got %d", RegisterSubnetValidatorTxExpiryWindow, secondsUntilExpiry) } - pop := signer.ProofOfPossession{ - PublicKey: msg.BLSPublicKey, - ProofOfPossession: tx.ProofOfPossession, - } - if err := pop.Verify(); err != nil { - return err - } - + // Verify that this warp message isn't being replayed. validationID := msg.ValidationID() expiry := state.ExpiryEntry{ Timestamp: msg.Expiry, @@ -892,21 +887,29 @@ func (e *standardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return fmt.Errorf("expiry for %s already exists", validationID) } + // Verify proof of possession provided by the transaction against the public + // key provided by the warp message. + pop := signer.ProofOfPossession{ + PublicKey: msg.BLSPublicKey, + ProofOfPossession: tx.ProofOfPossession, + } + if err := pop.Verify(); err != nil { + return err + } + + // Create the SoV. nodeID, err := ids.ToNodeID(msg.NodeID) if err != nil { return err } - 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 } - sov := state.SubnetOnlyValidator{ ValidationID: validationID, SubnetID: msg.SubnetID, @@ -919,12 +922,15 @@ func (e *standardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal MinNonce: 0, EndAccumulatedFee: 0, // If Balance is 0, this is 0 } + + // If the balance is non-zero, this validator should be initially active. if tx.Balance != 0 { - // We are attempting to add an active validator + // Verify that there is space for an active validator. if gas.Gas(e.state.NumActiveSubnetOnlyValidators()) >= e.backend.Config.ValidatorFeeConfig.Capacity { return errMaxNumActiveValidators } + // Mark the validator as active. currentFees := e.state.GetAccruedFees() sov.EndAccumulatedFee, err = math.Add(tx.Balance, currentFees) if err != nil { From c8f5157454c17b39ef607835e49c863b321e854e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 15:52:29 -0500 Subject: [PATCH 344/400] nit --- 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 3563666b054d..82a561b3969d 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -729,7 +729,7 @@ func (e *standardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { StartTime: startTime, Weight: vdr.Weight, MinNonce: 0, - EndAccumulatedFee: 0, // If Balance is 0, this is 0 + EndAccumulatedFee: 0, // If Balance is 0, this is will remain 0 } if vdr.Balance != 0 { // We are attempting to add an active validator From af6171a91d7a3eef64bb6edd14243b25ca4d0de1 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 19:22:56 -0500 Subject: [PATCH 345/400] Add execution tests --- vms/platformvm/signer/proof_of_possession.go | 4 +- .../signer/proof_of_possession_test.go | 2 +- .../txs/executor/standard_tx_executor.go | 27 +- .../txs/executor/standard_tx_executor_test.go | 538 ++++++++++++++++++ vms/platformvm/warp/payload/addressed_call.go | 2 +- vms/platformvm/warp/payload/hash.go | 2 +- vms/platformvm/warp/payload/payload.go | 2 +- vms/platformvm/warp/payload/payload_test.go | 4 +- 8 files changed, 562 insertions(+), 19 deletions(-) diff --git a/vms/platformvm/signer/proof_of_possession.go b/vms/platformvm/signer/proof_of_possession.go index 245d2a96c6f0..f63365d985f4 100644 --- a/vms/platformvm/signer/proof_of_possession.go +++ b/vms/platformvm/signer/proof_of_possession.go @@ -14,7 +14,7 @@ import ( var ( _ Signer = (*ProofOfPossession)(nil) - errInvalidProofOfPossession = errors.New("invalid proof of possession") + ErrInvalidProofOfPossession = errors.New("invalid proof of possession") ) type ProofOfPossession struct { @@ -52,7 +52,7 @@ func (p *ProofOfPossession) Verify() error { return err } if !bls.VerifyProofOfPossession(publicKey, signature, p.PublicKey[:]) { - return errInvalidProofOfPossession + return ErrInvalidProofOfPossession } p.publicKey = publicKey diff --git a/vms/platformvm/signer/proof_of_possession_test.go b/vms/platformvm/signer/proof_of_possession_test.go index 9f4f3feefa3c..9674d63985fc 100644 --- a/vms/platformvm/signer/proof_of_possession_test.go +++ b/vms/platformvm/signer/proof_of_possession_test.go @@ -35,7 +35,7 @@ func TestProofOfPossession(t *testing.T) { require.NoError(err) newBLSPOP.ProofOfPossession = blsPOP.ProofOfPossession err = newBLSPOP.Verify() - require.ErrorIs(err, errInvalidProofOfPossession) + require.ErrorIs(err, ErrInvalidProofOfPossession) } func TestNewProofOfPossessionDeterministic(t *testing.T) { diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 9b066d498ccb..da70c00ba713 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -43,12 +43,17 @@ const ( var ( _ txs.Visitor = (*standardTxExecutor)(nil) - errEmptyNodeID = errors.New("validator nodeID cannot be empty") - errMaxStakeDurationTooLarge = errors.New("max stake duration must be less than or equal to the global max stake duration") - 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") + errEmptyNodeID = errors.New("validator nodeID cannot be empty") + errMaxStakeDurationTooLarge = errors.New("max stake duration must be less than or equal to the global max stake duration") + 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") + errWrongWarpMessageSourceChainID = errors.New("wrong warp message source chain ID") + errWrongWarpMessageSourceAddress = errors.New("wrong warp message source address") + errWarpMessageExpired = errors.New("warp message expired") + errWarpMessageNotYetAllowed = errors.New("warp message not yet allowed") + errWarpMessageAlreadyIssued = errors.New("warp message already issued") ) // StandardTx executes the standard transaction [tx]. @@ -858,19 +863,19 @@ func (e *standardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return err } if warpMessage.SourceChainID != subnetConversion.ChainID { - return fmt.Errorf("expected chainID %s but got %s", subnetConversion.ChainID, warpMessage.SourceChainID) + return fmt.Errorf("%w expected %s but had %s", errWrongWarpMessageSourceChainID, subnetConversion.ChainID, warpMessage.SourceChainID) } if !bytes.Equal(addressedCall.SourceAddress, subnetConversion.Addr) { - return fmt.Errorf("expected address %s but got %s", subnetConversion.Addr, addressedCall.SourceAddress) + return fmt.Errorf("%w expected 0x%x but got 0x%x", errWrongWarpMessageSourceAddress, subnetConversion.Addr, addressedCall.SourceAddress) } // Verify that the message contains a valid expiry time. currentTimestampUnix := uint64(currentTimestamp.Unix()) if msg.Expiry <= currentTimestampUnix { - return fmt.Errorf("expected expiry to be after %d but got %d", currentTimestampUnix, msg.Expiry) + return fmt.Errorf("%w at %d and it is currently %d", errWarpMessageExpired, msg.Expiry, currentTimestampUnix) } if secondsUntilExpiry := msg.Expiry - currentTimestampUnix; secondsUntilExpiry > RegisterSubnetValidatorTxExpiryWindow { - return fmt.Errorf("expected expiry not to be more than %d seconds in the future but got %d", RegisterSubnetValidatorTxExpiryWindow, secondsUntilExpiry) + return fmt.Errorf("%w because time is %d seconds in the future but the limit is %d", errWarpMessageNotYetAllowed, secondsUntilExpiry, RegisterSubnetValidatorTxExpiryWindow) } // Verify that this warp message isn't being replayed. @@ -884,7 +889,7 @@ func (e *standardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal return err } if isDuplicate { - return fmt.Errorf("expiry for %s already exists", validationID) + return fmt.Errorf("%w for validationID %s", errWarpMessageAlreadyIssued, validationID) } // Verify proof of possession provided by the transaction against the public diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 8a2e574ccfc5..d969b6357095 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" + "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" @@ -25,6 +26,7 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/hashing" "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/components/avax" "github.com/ava-labs/avalanchego/vms/components/verify" @@ -40,10 +42,13 @@ 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" "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/common" + safemath "github.com/ava-labs/avalanchego/utils/math" txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) @@ -2670,3 +2675,536 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { }) } } + +func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { + var ( + fx = &secp256k1fx.Fx{} + vm = &secp256k1fx.TestVM{ + Log: logging.NoLog{}, + } + ) + require.NoError(t, fx.InitializeVM(vm)) + + var ( + ctx = snowtest.Context(t, constants.PlatformChainID) + defaultConfig = &config.Config{ + DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, + ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, + UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), + } + baseState = statetest.New(t, statetest.Config{ + Upgrades: defaultConfig.UpgradeConfig, + Context: ctx, + }) + wallet = txstest.NewWallet( + t, + ctx, + defaultConfig, + baseState, + secp256k1fx.NewKeychain(genesistest.DefaultFundedKeys...), + nil, // subnetIDs + nil, // chainIDs + ) + flowChecker = utxo.NewVerifier( + ctx, + &vm.Clk, + fx, + ) + + backend = &Backend{ + Config: defaultConfig, + Bootstrapped: utils.NewAtomic(true), + Fx: fx, + FlowChecker: flowChecker, + Ctx: ctx, + } + feeCalculator = state.PickFeeCalculator(defaultConfig, baseState) + ) + + // Create the initial state + diff, err := state.NewDiffOn(baseState) + require.NoError(t, err) + + // Create the subnet + createSubnetTx, err := wallet.IssueCreateSubnetTx( + &secp256k1fx.OutputOwners{}, + ) + require.NoError(t, err) + + // Execute the subnet creation + _, _, _, err = StandardTx( + backend, + feeCalculator, + createSubnetTx, + diff, + ) + require.NoError(t, err) + + // Create the subnet conversion + initialSK, err := bls.NewSecretKey() + require.NoError(t, err) + + const ( + initialWeight = 1 + initialBalance = units.Avax + ) + var ( + subnetID = createSubnetTx.ID() + chainID = ids.GenerateTestID() + address = utils.RandomBytes(32) + initialNodeID = ids.GenerateTestNodeID() + initialPoP = signer.NewProofOfPossession(initialSK) + validator = &txs.ConvertSubnetValidator{ + NodeID: initialNodeID.Bytes(), + Weight: initialWeight, + Balance: initialBalance, + Signer: *initialPoP, + RemainingBalanceOwner: message.PChainOwner{}, + DeactivationOwner: message.PChainOwner{}, + } + ) + convertSubnetTx, err := wallet.IssueConvertSubnetTx( + subnetID, + chainID, + address, + []*txs.ConvertSubnetValidator{ + validator, + }, + ) + require.NoError(t, err) + + // Execute the subnet conversion + _, _, _, err = StandardTx( + backend, + feeCalculator, + convertSubnetTx, + diff, + ) + require.NoError(t, err) + require.NoError(t, diff.Apply(baseState)) + require.NoError(t, baseState.Commit()) + + var ( + nodeID = ids.GenerateTestNodeID() + lastAcceptedTime = baseState.GetTimestamp() + expiryTime = lastAcceptedTime.Add(5 * time.Minute) + expiry = uint64(expiryTime.Unix()) // The warp message will expire in 5 minutes + ) + + const weight = 1 + + // Create the Warp message + sk, err := bls.NewSecretKey() + require.NoError(t, err) + pop := signer.NewProofOfPossession(sk) + pk := bls.PublicFromSecretKey(sk) + pkBytes := bls.PublicKeyToUncompressedBytes(pk) + + remainingBalanceOwner := message.PChainOwner{} + remainingBalanceOwnerBytes, err := txs.Codec.Marshal(txs.CodecVersion, &remainingBalanceOwner) + require.NoError(t, err) + + deactivationOwner := message.PChainOwner{} + deactivationOwnerBytes, err := txs.Codec.Marshal(txs.CodecVersion, &deactivationOwner) + require.NoError(t, err) + + addressedCallPayload := must[*message.RegisterSubnetValidator](t)(message.NewRegisterSubnetValidator( + subnetID, + nodeID, + pop.PublicKey, + expiry, + remainingBalanceOwner, + deactivationOwner, + weight, + )) + unsignedWarp := must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + address, + addressedCallPayload.Bytes(), + )).Bytes(), + )) + warpSignature := &warp.BitSetSignature{ + Signers: set.NewBits(0).Bytes(), + Signature: ([bls.SignatureLen]byte)(bls.SignatureToBytes( + bls.Sign( + sk, + unsignedWarp.Bytes(), + ), + )), + } + warpMessage := must[*warp.Message](t)(warp.NewMessage( + unsignedWarp, + warpSignature, + )) + + validationID := addressedCallPayload.ValidationID() + tests := []struct { + name string + balance uint64 + message []byte + builderOptions []common.Option + updateTx func(*txs.RegisterSubnetValidatorTx) + updateExecutor func(*standardTxExecutor) error + expectedErr error + }{ + { + name: "invalid prior to E-Upgrade", + 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) error { + e.backend.Ctx = snowtest.Context(t, ids.GenerateTestID()) + return nil + }, + expectedErr: avax.ErrWrongChainID, + }, + { + name: "invalid memo length", + builderOptions: []common.Option{ + common.WithMemo([]byte("memo!")), + }, + expectedErr: avax.ErrMemoTooLarge, + }, + { + name: "invalid fee calculation", + updateExecutor: func(e *standardTxExecutor) error { + e.feeCalculator = txfee.NewStaticCalculator(e.backend.Config.StaticFeeConfig) + return nil + }, + expectedErr: txfee.ErrUnsupportedTx, + }, + { + name: "fee calculation overflow", + updateTx: func(tx *txs.RegisterSubnetValidatorTx) { + tx.Balance = math.MaxUint64 + }, + expectedErr: safemath.ErrOverflow, + }, + { + name: "insufficient fee", + updateExecutor: func(e *standardTxExecutor) error { + e.feeCalculator = txfee.NewDynamicCalculator( + e.backend.Config.DynamicFeeConfig.Weights, + 100*genesis.LocalParams.DynamicFeeConfig.MinPrice, + ) + return nil + }, + expectedErr: utxo.ErrInsufficientUnlockedFunds, + }, + { + name: "invalid warp message", + message: []byte{}, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "invalid warp payload", + message: must[*warp.Message](t)(warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.Hash](t)(payload.NewHash(ids.Empty)).Bytes(), + )), + warpSignature, + )).Bytes(), + expectedErr: payload.ErrWrongType, + }, + { + name: "invalid addressed call", + message: must[*warp.Message](t)(warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + address, + must[*message.SubnetConversion](t)(message.NewSubnetConversion(ids.Empty)).Bytes(), + )).Bytes(), + )), + warpSignature, + )).Bytes(), + expectedErr: message.ErrWrongType, + }, + { + name: "invalid addressed call payload", + message: must[*warp.Message](t)(warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + address, + must[*message.RegisterSubnetValidator](t)(message.NewRegisterSubnetValidator( + subnetID, + nodeID, + pop.PublicKey, + expiry, + remainingBalanceOwner, + deactivationOwner, + 0, // weight = 0 is invalid + )).Bytes(), + )).Bytes(), + )), + warpSignature, + )).Bytes(), + expectedErr: message.ErrInvalidWeight, + }, + { + name: "subnet conversion not found", + message: must[*warp.Message](t)(warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + address, + must[*message.RegisterSubnetValidator](t)(message.NewRegisterSubnetValidator( + ids.GenerateTestID(), // invalid subnetID + nodeID, + pop.PublicKey, + expiry, + remainingBalanceOwner, + deactivationOwner, + weight, + )).Bytes(), + )).Bytes(), + )), + warpSignature, + )).Bytes(), + expectedErr: database.ErrNotFound, + }, + { + name: "invalid source chain", + updateExecutor: func(e *standardTxExecutor) error { + e.state.SetSubnetConversion(subnetID, state.SubnetConversion{}) + return nil + }, + expectedErr: errWrongWarpMessageSourceChainID, + }, + { + name: "invalid source chain", + updateExecutor: func(e *standardTxExecutor) error { + e.state.SetSubnetConversion(subnetID, state.SubnetConversion{ + ChainID: chainID, + }) + return nil + }, + expectedErr: errWrongWarpMessageSourceAddress, + }, + { + name: "message expired", + updateExecutor: func(e *standardTxExecutor) error { + e.state.SetTimestamp(expiryTime) + return nil + }, + expectedErr: errWarpMessageExpired, + }, + { + name: "message too far in the future", + message: must[*warp.Message](t)(warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + address, + must[*message.RegisterSubnetValidator](t)(message.NewRegisterSubnetValidator( + subnetID, + nodeID, + pop.PublicKey, + math.MaxUint64, // expiry too far in the future + remainingBalanceOwner, + deactivationOwner, + weight, + )).Bytes(), + )).Bytes(), + )), + warpSignature, + )).Bytes(), + expectedErr: errWarpMessageNotYetAllowed, + }, + { + name: "duplicate SoV", + balance: 1, + updateExecutor: func(e *standardTxExecutor) error { + e.state.PutExpiry(state.ExpiryEntry{ + Timestamp: expiry, + ValidationID: validationID, + }) + return nil + }, + expectedErr: errWarpMessageAlreadyIssued, + }, + { + name: "invalid PoP", + message: must[*warp.Message](t)(warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + address, + must[*message.RegisterSubnetValidator](t)(message.NewRegisterSubnetValidator( + subnetID, + nodeID, + initialPoP.PublicKey, // Wrong public key + expiry, + remainingBalanceOwner, + deactivationOwner, + weight, + )).Bytes(), + )).Bytes(), + )), + warpSignature, + )).Bytes(), + expectedErr: signer.ErrInvalidProofOfPossession, + }, + { + name: "too many active validators", + balance: 1, + updateExecutor: func(e *standardTxExecutor) error { + e.backend.Config = &config.Config{ + DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, + ValidatorFeeConfig: validatorfee.Config{ + Capacity: 0, + Target: genesis.LocalParams.ValidatorFeeConfig.Target, + MinPrice: genesis.LocalParams.ValidatorFeeConfig.MinPrice, + ExcessConversionConstant: genesis.LocalParams.ValidatorFeeConfig.ExcessConversionConstant, + }, + UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), + } + return nil + }, + expectedErr: errMaxNumActiveValidators, + }, + { + name: "accrued fees overflow", + balance: 1, + updateExecutor: func(e *standardTxExecutor) error { + e.state.SetAccruedFees(math.MaxUint64) + return nil + }, + expectedErr: safemath.ErrOverflow, + }, + { + name: "duplicate SoV", + balance: 1, + updateExecutor: func(e *standardTxExecutor) error { + return e.state.PutSubnetOnlyValidator(state.SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: subnetID, + NodeID: nodeID, + PublicKey: bls.PublicKeyToUncompressedBytes(bls.PublicFromSecretKey(initialSK)), + Weight: 1, + }) + }, + expectedErr: state.ErrDuplicateSubnetOnlyValidator, + }, + { + name: "valid tx", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + // Create the RegisterSubnetValidatorTx + wallet := txstest.NewWallet( + t, + ctx, + defaultConfig, + baseState, + secp256k1fx.NewKeychain(genesistest.DefaultFundedKeys...), + nil, // subnetIDs + nil, // chainIDs + ) + + message := test.message + if message == nil { + message = warpMessage.Bytes() + } + registerSubnetValidatorTx, err := wallet.IssueRegisterSubnetValidatorTx( + test.balance, + pop.ProofOfPossession, + message, + test.builderOptions..., + ) + require.NoError(err) + + if test.updateTx != nil { + unsignedTx := registerSubnetValidatorTx.Unsigned.(*txs.RegisterSubnetValidatorTx) + test.updateTx(unsignedTx) + } + + diff, err := state.NewDiffOn(baseState) + require.NoError(err) + + executor := &standardTxExecutor{ + backend: &Backend{ + Config: defaultConfig, + Bootstrapped: utils.NewAtomic(true), + Fx: fx, + FlowChecker: flowChecker, + Ctx: ctx, + }, + feeCalculator: state.PickFeeCalculator(defaultConfig, baseState), + tx: registerSubnetValidatorTx, + state: diff, + } + if test.updateExecutor != nil { + require.NoError(test.updateExecutor(executor)) + } + + err = registerSubnetValidatorTx.Unsigned.Visit(executor) + require.ErrorIs(err, test.expectedErr) + if err != nil { + return + } + + for utxoID := range registerSubnetValidatorTx.InputIDs() { + _, err := diff.GetUTXO(utxoID) + require.ErrorIs(err, database.ErrNotFound) + } + + for _, expectedUTXO := range registerSubnetValidatorTx.UTXOs() { + utxoID := expectedUTXO.InputID() + utxo, err := diff.GetUTXO(utxoID) + require.NoError(err) + require.Equal(expectedUTXO, utxo) + } + + sov, err := diff.GetSubnetOnlyValidator(validationID) + require.NoError(err) + + var expectedEndAccumulatedFee uint64 + if test.balance != 0 { + expectedEndAccumulatedFee = test.balance + diff.GetAccruedFees() + } + require.Equal( + state.SubnetOnlyValidator{ + ValidationID: validationID, + SubnetID: subnetID, + NodeID: nodeID, + PublicKey: pkBytes, + RemainingBalanceOwner: remainingBalanceOwnerBytes, + DeactivationOwner: deactivationOwnerBytes, + StartTime: uint64(diff.GetTimestamp().Unix()), + Weight: weight, + MinNonce: 0, + EndAccumulatedFee: expectedEndAccumulatedFee, + }, + sov, + ) + }) + } +} + +func must[T any](t require.TestingT) func(T, error) T { + return func(val T, err error) T { + require.NoError(t, err) + return val + } +} diff --git a/vms/platformvm/warp/payload/addressed_call.go b/vms/platformvm/warp/payload/addressed_call.go index b3617ce487da..9950be42ec0d 100644 --- a/vms/platformvm/warp/payload/addressed_call.go +++ b/vms/platformvm/warp/payload/addressed_call.go @@ -37,7 +37,7 @@ func ParseAddressedCall(b []byte) (*AddressedCall, error) { } payload, ok := payloadIntf.(*AddressedCall) if !ok { - return nil, fmt.Errorf("%w: %T", errWrongType, payloadIntf) + return nil, fmt.Errorf("%w: %T", ErrWrongType, payloadIntf) } return payload, nil } diff --git a/vms/platformvm/warp/payload/hash.go b/vms/platformvm/warp/payload/hash.go index 330f74fd869d..73804d169bdf 100644 --- a/vms/platformvm/warp/payload/hash.go +++ b/vms/platformvm/warp/payload/hash.go @@ -33,7 +33,7 @@ func ParseHash(b []byte) (*Hash, error) { } payload, ok := payloadIntf.(*Hash) if !ok { - return nil, fmt.Errorf("%w: %T", errWrongType, payloadIntf) + return nil, fmt.Errorf("%w: %T", ErrWrongType, payloadIntf) } return payload, nil } diff --git a/vms/platformvm/warp/payload/payload.go b/vms/platformvm/warp/payload/payload.go index c5c09464803e..0f7831c6b343 100644 --- a/vms/platformvm/warp/payload/payload.go +++ b/vms/platformvm/warp/payload/payload.go @@ -8,7 +8,7 @@ import ( "fmt" ) -var errWrongType = errors.New("wrong payload type") +var ErrWrongType = errors.New("wrong payload type") // Payload provides a common interface for all payloads implemented by this // package. diff --git a/vms/platformvm/warp/payload/payload_test.go b/vms/platformvm/warp/payload/payload_test.go index 86b584ae33db..6ae1fcc09c3c 100644 --- a/vms/platformvm/warp/payload/payload_test.go +++ b/vms/platformvm/warp/payload/payload_test.go @@ -33,10 +33,10 @@ func TestParseWrongPayloadType(t *testing.T) { require.NoError(err) _, err = ParseAddressedCall(hashPayload.Bytes()) - require.ErrorIs(err, errWrongType) + require.ErrorIs(err, ErrWrongType) _, err = ParseHash(addressedPayload.Bytes()) - require.ErrorIs(err, errWrongType) + require.ErrorIs(err, ErrWrongType) } func TestParse(t *testing.T) { From e3f6ab3730e5dc45b1afd26a9426183a1e0bf1b6 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 19:30:03 -0500 Subject: [PATCH 346/400] nit --- vms/platformvm/txs/executor/standard_tx_executor.go | 3 ++- vms/platformvm/txs/executor/standard_tx_executor_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index da70c00ba713..b64972452eba 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") + errCouldNotLoadSubnetConversion = errors.New("could not load subnet conversion") errWrongWarpMessageSourceChainID = errors.New("wrong warp message source chain ID") errWrongWarpMessageSourceAddress = errors.New("wrong warp message source address") errWarpMessageExpired = errors.New("warp message expired") @@ -860,7 +861,7 @@ func (e *standardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal // address. subnetConversion, err := e.state.GetSubnetConversion(msg.SubnetID) if err != nil { - return err + return fmt.Errorf("%w for %s with: %w", errCouldNotLoadSubnetConversion, msg.SubnetID, err) } if warpMessage.SourceChainID != subnetConversion.ChainID { return fmt.Errorf("%w expected %s but had %s", errWrongWarpMessageSourceChainID, subnetConversion.ChainID, warpMessage.SourceChainID) diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index d969b6357095..4ba046722c4d 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -2976,7 +2976,7 @@ func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { )), warpSignature, )).Bytes(), - expectedErr: database.ErrNotFound, + expectedErr: errCouldNotLoadSubnetConversion, }, { name: "invalid source chain", From fc9440cabaa2bb3c3fcf413f89aa839a419debba Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 19:32:40 -0500 Subject: [PATCH 347/400] nit --- vms/platformvm/txs/executor/standard_tx_executor_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 4ba046722c4d..59aa8d45445d 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -3198,6 +3198,13 @@ func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { }, sov, ) + + hasExpiry, err := diff.HasExpiry(state.ExpiryEntry{ + Timestamp: expiry, + ValidationID: validationID, + }) + require.NoError(err) + require.True(hasExpiry) }) } } From f41cff6ca46f9fcb2a7e1f2d301f051d519dc238 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 19:42:46 -0500 Subject: [PATCH 348/400] remove usage of defer --- tests/e2e/p/l1.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 6ee961450a34..5543218914d6 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -204,11 +204,6 @@ var _ = e2e.DescribePChain("[L1]", func() { }), ) require.NoError(err) - defer func() { - genesisPeerMessages.Close() - genesisPeer.StartClose() - require.NoError(genesisPeer.AwaitClosed(tc.DefaultContext())) - }() address := []byte{} tc.By("issuing a ConvertSubnetTx", func() { @@ -372,6 +367,10 @@ var _ = e2e.DescribePChain("[L1]", func() { }) }) + genesisPeerMessages.Close() + genesisPeer.StartClose() + require.NoError(genesisPeer.AwaitClosed(tc.DefaultContext())) + _ = e2e.CheckBootstrapIsPossible(tc, env.GetNetwork()) }) }) From 38f63f9ad0e020850347eb5e97c8b25a857f824b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 20:24:34 -0500 Subject: [PATCH 349/400] nit --- tests/e2e/p/l1.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 098a2875c2de..98ecfc634abf 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -330,15 +330,13 @@ var _ = e2e.DescribePChain("[L1]", func() { require.True(ok) tc.By("creating the signed warp message to register the validator") - signers := set.NewBits(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, + Signers: set.NewBits(0).Bytes(), // [signers] has weight from the genesis peer + Signature: ([bls.SignatureLen]byte)( + bls.SignatureToBytes(registerSubnetValidatorSignature), + ), }, ) require.NoError(err) @@ -401,8 +399,7 @@ var _ = e2e.DescribePChain("[L1]", func() { require.True(ok) 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 + signers := set.NewBits(0) // [signers] has weight from the genesis peer var sigBytes [bls.SignatureLen]byte copy(sigBytes[:], bls.SignatureToBytes(setSubnetValidatorWeightSignature)) From aa4e85907db531288556d7e8aa918d06d47333a9 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 20:25:48 -0500 Subject: [PATCH 350/400] nit --- tests/e2e/p/l1.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 98ecfc634abf..99c94874eebd 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -399,15 +399,13 @@ var _ = e2e.DescribePChain("[L1]", func() { require.True(ok) tc.By("creating the signed warp message to increase the weight of the validator") - signers := set.NewBits(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, + Signers: set.NewBits(0).Bytes(), // [signers] has weight from the genesis peer + Signature: ([bls.SignatureLen]byte)( + bls.SignatureToBytes(setSubnetValidatorWeightSignature), + ), }, ) require.NoError(err) From a35c877873900a37876b0e9f003e67457f44471c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 20:27:08 -0500 Subject: [PATCH 351/400] nit --- tests/e2e/p/l1.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 5543218914d6..8a102b4e080a 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -328,18 +328,15 @@ var _ = e2e.DescribePChain("[L1]", func() { require.True(ok) tc.By("creating the signed warp message to register the validator") - signers := set.NewBits(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, + Signers: set.NewBits(0).Bytes(), // [signers] has weight from the genesis peer + Signature: ([bls.SignatureLen]byte)( + bls.SignatureToBytes(registerSubnetValidatorSignature), + ), }, ) - require.NoError(err) tc.By("issuing a RegisterSubnetValidatorTx", func() { _, err := pWallet.IssueRegisterSubnetValidatorTx( From 28b478b63c86ea973b39562de63c7ee8fbe8c4ae Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 20:28:14 -0500 Subject: [PATCH 352/400] oops --- tests/e2e/p/l1.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 8a102b4e080a..73c1e08eed1e 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -337,6 +337,7 @@ var _ = e2e.DescribePChain("[L1]", func() { ), }, ) + require.NoError(err) tc.By("issuing a RegisterSubnetValidatorTx", func() { _, err := pWallet.IssueRegisterSubnetValidatorTx( From 3b1d5ca1c480717970330aee702f9e049d3d929b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 20:29:00 -0500 Subject: [PATCH 353/400] nit --- tests/e2e/p/l1.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 99c94874eebd..ce3fefd8bc2d 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -399,7 +399,7 @@ var _ = e2e.DescribePChain("[L1]", func() { require.True(ok) tc.By("creating the signed warp message to increase the weight of the validator") - registerSubnetValidator, err := warp.NewMessage( + setSubnetValidatorWeight, err := warp.NewMessage( unsignedSubnetValidatorWeight, &warp.BitSetSignature{ Signers: set.NewBits(0).Bytes(), // [signers] has weight from the genesis peer @@ -412,7 +412,7 @@ var _ = e2e.DescribePChain("[L1]", func() { tc.By("issuing a SetSubnetValidatorWeightTx", func() { _, err := pWallet.IssueSetSubnetValidatorWeightTx( - registerSubnetValidator.Bytes(), + setSubnetValidatorWeight.Bytes(), ) require.NoError(err) }) From 95716935354405609fe48cc7d340f7b9a7cc9bc3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 20:37:44 -0500 Subject: [PATCH 354/400] nit --- tests/e2e/p/l1.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index ce3fefd8bc2d..96156ddff36b 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -369,7 +369,7 @@ var _ = e2e.DescribePChain("[L1]", func() { var nextNonce uint64 setWeight := func(validationID ids.ID, weight uint64) { - tc.By("creating the unsigned warp message") + tc.By("creating the unsigned SubnetValidatorWeightMessage") unsignedSubnetValidatorWeight := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( networkID, chainID, From 30ac509ac3d4643aedbe621e0746adc7640f327e Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 7 Nov 2024 22:21:11 -0500 Subject: [PATCH 355/400] Address PR comments --- vms/platformvm/txs/executor/standard_tx_executor_test.go | 4 ++-- vms/platformvm/txs/fee/complexity.go | 2 ++ vms/platformvm/validators/manager.go | 5 ++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 59aa8d45445d..ba4f5853b7b6 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -2987,7 +2987,7 @@ func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { expectedErr: errWrongWarpMessageSourceChainID, }, { - name: "invalid source chain", + name: "invalid source address", updateExecutor: func(e *standardTxExecutor) error { e.state.SetSubnetConversion(subnetID, state.SubnetConversion{ ChainID: chainID, @@ -3005,7 +3005,7 @@ func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { expectedErr: errWarpMessageExpired, }, { - name: "message too far in the future", + name: "message expiry too far in the future", message: must[*warp.Message](t)(warp.NewMessage( must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( ctx.NetworkID, diff --git a/vms/platformvm/txs/fee/complexity.go b/vms/platformvm/txs/fee/complexity.go index 66465ba0bb67..5083296d2936 100644 --- a/vms/platformvm/txs/fee/complexity.go +++ b/vms/platformvm/txs/fee/complexity.go @@ -1,6 +1,8 @@ // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. +// TODO: Before Etna, address all TODOs in this package and ensure ACP-103 +// compliance. package fee import ( diff --git a/vms/platformvm/validators/manager.go b/vms/platformvm/validators/manager.go index 6e620bdc7c70..be37e6ee73e2 100644 --- a/vms/platformvm/validators/manager.go +++ b/vms/platformvm/validators/manager.go @@ -33,9 +33,8 @@ const ( // recommended minimum height will lag behind the last accepted block. MinRecentlyAcceptedWindowSize = 0 // RecentlyAcceptedWindowTTL is the amount of time after a block is accepted - // to avoid recommending it as the minimum height. If the evicting, or not - // evicting, the block would violate either the maxiumum or minimum window - // size, the block will not be evicted. + // to avoid recommending it as the minimum height. The size constraints take + // precedence over this time constraint. RecentlyAcceptedWindowTTL = 30 * time.Second validatorSetsCacheSize = 64 From f16cc58058e02d25c772dbd5870c76c2595f8bdc Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 8 Nov 2024 09:53:48 -0500 Subject: [PATCH 356/400] Address PR comments --- .../txs/executor/standard_tx_executor.go | 56 +++++++++++-------- wallet/chain/p/builder/builder.go | 2 +- .../set-subnet-validator-weight/main.go | 36 +++++------- 3 files changed, 50 insertions(+), 44 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 9e7632007c42..dda138e9a36f 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -862,15 +862,8 @@ func (e *standardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal // Verify that the warp message was sent from the expected chain and // address. - subnetConversion, err := e.state.GetSubnetConversion(msg.SubnetID) - if err != nil { - return fmt.Errorf("%w for %s with: %w", errCouldNotLoadSubnetConversion, msg.SubnetID, err) - } - if warpMessage.SourceChainID != subnetConversion.ChainID { - return fmt.Errorf("%w expected %s but had %s", errWrongWarpMessageSourceChainID, subnetConversion.ChainID, warpMessage.SourceChainID) - } - if !bytes.Equal(addressedCall.SourceAddress, subnetConversion.Addr) { - return fmt.Errorf("%w expected 0x%x but got 0x%x", errWrongWarpMessageSourceAddress, subnetConversion.Addr, addressedCall.SourceAddress) + if err := verifyL1Conversion(e.state, msg.SubnetID, warpMessage.SourceChainID, addressedCall.SourceAddress); err != nil { + return err } // Verify that the message contains a valid expiry time. @@ -1022,16 +1015,11 @@ func (e *standardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat return fmt.Errorf("expected nonce to be at least %d but got %d", sov.MinNonce, msg.Nonce) } - subnetConversion, err := e.state.GetSubnetConversion(sov.SubnetID) - if err != nil { + // Verify that the warp message was sent from the expected chain and + // address. + if err := verifyL1Conversion(e.state, sov.SubnetID, warpMessage.SourceChainID, addressedCall.SourceAddress); err != nil { return err } - if warpMessage.SourceChainID != subnetConversion.ChainID { - return fmt.Errorf("expected chainID %s but got %s", subnetConversion.ChainID, warpMessage.SourceChainID) - } - if !bytes.Equal(addressedCall.SourceAddress, subnetConversion.Addr) { - return fmt.Errorf("expected address %s but got %s", subnetConversion.Addr, addressedCall.SourceAddress) - } txID := e.tx.ID() @@ -1055,9 +1043,9 @@ func (e *standardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat 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. + // This check should be unreachable. However, it prevents AVAX + // from being minted due to state corruption. This also prevents + // invalid UTXOs from being created (with 0 value). return fmt.Errorf("%w: validator should have already been disabled", errStateCorruption) } remainingBalance := sov.EndAccumulatedFee - accruedFees @@ -1082,8 +1070,11 @@ func (e *standardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat } } - // If the weight is being set to 0, the validator is being removed and the - // nonce doesn't matter. + // If the weight is being set to 0, it is possible for the nonce increment + // to overflow. However, the validator is being removed and the nonce + // doesn't matter. If weight is not 0, [msg.Nonce] is enforced by + // [msg.Verify()] to be less than MaxUInt64 and can therefore be incremented + // without overflow. sov.MinNonce = msg.Nonce + 1 sov.Weight = msg.Weight if err := e.state.PutSubnetOnlyValidator(sov); err != nil { @@ -1168,3 +1159,24 @@ func (e *standardTxExecutor) putStaker(stakerTx txs.Staker) error { } return nil } + +// verifyL1Conversion verifies that the L1 conversion of [subnetID] references +// the [expectedChainID] and [expectedAddress]. +func verifyL1Conversion( + state state.Chain, + subnetID ids.ID, + expectedChainID ids.ID, + expectedAddress []byte, +) error { + subnetConversion, err := state.GetSubnetConversion(subnetID) + if err != nil { + return fmt.Errorf("%w for %s with: %w", errCouldNotLoadSubnetConversion, subnetID, err) + } + if expectedChainID != subnetConversion.ChainID { + return fmt.Errorf("%w expected %s but had %s", errWrongWarpMessageSourceChainID, subnetConversion.ChainID, expectedChainID) + } + if !bytes.Equal(expectedAddress, subnetConversion.Addr) { + return fmt.Errorf("%w expected 0x%x but got 0x%x", errWrongWarpMessageSourceAddress, subnetConversion.Addr, expectedAddress) + } + return nil +} diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index b8f0227f0969..9bb1364d9fe7 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -962,7 +962,7 @@ func (b *builder) NewSetSubnetValidatorWeightTx( if err != nil { return nil, err } - complexity, err := fee.IntrinsicRegisterSubnetValidatorTxComplexities.Add( + complexity, err := fee.IntrinsicSetSubnetValidatorWeightTxComplexities.Add( &memoComplexity, &warpComplexity, ) 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..a796ae9b51e7 100644 --- a/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go +++ b/wallet/subnet/primary/examples/set-subnet-validator-weight/main.go @@ -8,7 +8,6 @@ import ( "encoding/hex" "encoding/json" "log" - "os" "time" "github.com/ava-labs/avalanchego/genesis" @@ -27,28 +26,24 @@ func main() { uri := primary.LocalAPIURI kc := secp256k1fx.NewKeychain(key) chainID := ids.FromStringOrPanic("2BMFrJ9xeh5JdwZEx6uuFcjfZC2SV2hdbMT8ee5HrvjtfJb5br") - addressHex := "" + address := []byte{} validationID := ids.FromStringOrPanic("2Y3ZZZXxpzm46geqVuqFXeSFVbeKihgrfeXRDaiF4ds6R2N8M5") nonce := uint64(1) weight := uint64(2) + blsSKHex := "3f783929b295f16cd1172396acb23b20eed057b9afb1caa419e9915f92860b35" - address, err := hex.DecodeString(addressHex) + blsSKBytes, err := hex.DecodeString(blsSKHex) if err != nil { - log.Fatalf("failed to decode address %q: %s\n", addressHex, err) + log.Fatalf("failed to decode secret key: %s\n", 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) + sk, err := bls.SecretKeyFromBytes(blsSKBytes) 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]. + // [uri] is hosting. walletSyncStartTime := time.Now() ctx := context.Background() wallet, err := primary.MakeWallet(ctx, &primary.WalletConfig{ @@ -96,19 +91,18 @@ 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{ - Signers: signers.Bytes(), - Signature: sigBytes, + Signers: set.NewBits(0).Bytes(), + Signature: ([bls.SignatureLen]byte)( + bls.SignatureToBytes( + bls.Sign( + sk, + unsignedWarp.Bytes(), + ), + ), + ), }, ) if err != nil { From 2947902c81bec399f7c08e2d60af41f213d16e7a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 8 Nov 2024 09:59:50 -0500 Subject: [PATCH 357/400] comment nits --- vms/platformvm/txs/executor/standard_tx_executor.go | 7 +++++-- 1 file 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 dda138e9a36f..222a5458d06a 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -991,6 +991,7 @@ func (e *standardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat return err } + // Parse the warp message. warpMessage, err := warp.ParseMessage(tx.Message) if err != nil { return err @@ -1007,6 +1008,7 @@ func (e *standardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat return err } + // Verify that the message contains a valid nonce for a current validator. sov, err := e.state.GetSubnetOnlyValidator(msg.ValidationID) if err != nil { return err @@ -1023,8 +1025,9 @@ func (e *standardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat txID := e.tx.ID() - // We are removing the validator + // Check if we are removing the validator. if msg.Weight == 0 { + // Verify that we are not removing the last validator. weight, err := e.state.WeightOfSubnetOnlyValidators(sov.SubnetID) if err != nil { return err @@ -1033,7 +1036,7 @@ func (e *standardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat return errRemovingLastValidator } - // The validator is currently active, we need to refund the remaining + // If the validator is currently active, we need to refund the remaining // balance. if sov.EndAccumulatedFee != 0 { var remainingBalanceOwner message.PChainOwner From 6357f5a22a0ccf4035e98a5f184594bef87dde6b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 8 Nov 2024 10:09:08 -0500 Subject: [PATCH 358/400] improve error reporting --- 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 222a5458d06a..06d234353603 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -56,6 +56,7 @@ var ( errWarpMessageExpired = errors.New("warp message expired") errWarpMessageNotYetAllowed = errors.New("warp message not yet allowed") errWarpMessageAlreadyIssued = errors.New("warp message already issued") + errWarpMessageContainsStaleNonce = errors.New("warp message contains stale nonce") errRemovingLastValidator = errors.New("attempting to remove the last SoV from a converted subnet") errStateCorruption = errors.New("state corruption") ) @@ -1014,7 +1015,7 @@ func (e *standardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat return err } if msg.Nonce < sov.MinNonce { - return fmt.Errorf("expected nonce to be at least %d but got %d", sov.MinNonce, msg.Nonce) + return fmt.Errorf("%w %d must be at least %d", errWarpMessageContainsStaleNonce, msg.Nonce, sov.MinNonce) } // Verify that the warp message was sent from the expected chain and From eaa7ea3f788adfcd2f77d9b131cb6773b2464e5b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 8 Nov 2024 10:17:32 -0500 Subject: [PATCH 359/400] nit --- wallet/chain/p/builder/builder.go | 3 +-- wallet/chain/p/wallet/wallet.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index 9bb1364d9fe7..8ca24ba8f7d9 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -179,8 +179,7 @@ type Builder interface { options ...common.Option, ) (*txs.RegisterSubnetValidatorTx, error) - // NewSetSubnetValidatorWeightTx sets the weight of a validator on a - // Permissionless L1. + // NewSetSubnetValidatorWeightTx sets the weight of a validator on an L1. // // - [message] is the Warp message that authorizes this validator's weight // to be changed diff --git a/wallet/chain/p/wallet/wallet.go b/wallet/chain/p/wallet/wallet.go index 72842f24a426..26ad5a4b5a97 100644 --- a/wallet/chain/p/wallet/wallet.go +++ b/wallet/chain/p/wallet/wallet.go @@ -167,7 +167,7 @@ type Wallet interface { ) (*txs.Tx, error) // IssueSetSubnetValidatorWeightTx creates, signs, and issues a transaction - // that sets the weight of a validator on a Permissionless L1. + // that sets the weight of a validator on an L1. // // - [message] is the Warp message that authorizes this validator's weight // to be changed From e2451ab3b347c6f11eae3f9d76a58b4c05c8eac5 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 8 Nov 2024 10:25:30 -0500 Subject: [PATCH 360/400] Add SetSubnetValidatorWeightTx serialization and SyntacticVerify tests --- .../set_subnet_validator_weight_tx_test.go | 365 ++++++++++++++++++ .../set_subnet_validator_weight_tx_test.json | 76 ++++ 2 files changed, 441 insertions(+) create mode 100644 vms/platformvm/txs/set_subnet_validator_weight_tx_test.go create mode 100644 vms/platformvm/txs/set_subnet_validator_weight_tx_test.json diff --git a/vms/platformvm/txs/set_subnet_validator_weight_tx_test.go b/vms/platformvm/txs/set_subnet_validator_weight_tx_test.go new file mode 100644 index 000000000000..13dd4afc7766 --- /dev/null +++ b/vms/platformvm/txs/set_subnet_validator_weight_tx_test.go @@ -0,0 +1,365 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txs + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + _ "embed" + + "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/units" + "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/vms/platformvm/stakeable" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/vms/types" +) + +//go:embed set_subnet_validator_weight_tx_test.json +var setSubnetValidatorWeightTxJSON []byte + +func TestSetSubnetValidatorWeightTxSerialization(t *testing.T) { + require := require.New(t) + + var ( + message = []byte("message") + addr = ids.ShortID{ + 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0x44, 0x55, 0x66, 0x77, + } + avaxAssetID = ids.ID{ + 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, + 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, + 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, + 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, + } + customAssetID = ids.ID{ + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + } + txID = ids.ID{ + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + } + ) + + var unsignedTx UnsignedTx = &SetSubnetValidatorWeightTx{ + BaseTx: BaseTx{ + BaseTx: avax.BaseTx{ + NetworkID: constants.UnitTestID, + BlockchainID: constants.PlatformChainID, + Outs: []*avax.TransferableOutput{ + { + Asset: avax.Asset{ + ID: avaxAssetID, + }, + Out: &stakeable.LockOut{ + Locktime: 87654321, + TransferableOut: &secp256k1fx.TransferOutput{ + Amt: 1, + OutputOwners: secp256k1fx.OutputOwners{ + Locktime: 12345678, + Threshold: 0, + Addrs: []ids.ShortID{}, + }, + }, + }, + }, + { + Asset: avax.Asset{ + ID: customAssetID, + }, + Out: &stakeable.LockOut{ + Locktime: 876543210, + TransferableOut: &secp256k1fx.TransferOutput{ + Amt: 0xffffffffffffffff, + OutputOwners: secp256k1fx.OutputOwners{ + Locktime: 0, + Threshold: 1, + Addrs: []ids.ShortID{ + addr, + }, + }, + }, + }, + }, + }, + Ins: []*avax.TransferableInput{ + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: 1, + }, + Asset: avax.Asset{ + ID: avaxAssetID, + }, + In: &secp256k1fx.TransferInput{ + Amt: units.Avax, + Input: secp256k1fx.Input{ + SigIndices: []uint32{2, 5}, + }, + }, + }, + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: 2, + }, + Asset: avax.Asset{ + ID: customAssetID, + }, + In: &stakeable.LockIn{ + Locktime: 876543210, + TransferableIn: &secp256k1fx.TransferInput{ + Amt: 0xefffffffffffffff, + Input: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + }, + }, + }, + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: 3, + }, + Asset: avax.Asset{ + ID: customAssetID, + }, + In: &secp256k1fx.TransferInput{ + Amt: 0x1000000000000000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{}, + }, + }, + }, + }, + Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"), + }, + }, + Message: message, + } + txBytes, err := Codec.Marshal(CodecVersion, &unsignedTx) + require.NoError(err) + + expectedBytes := []byte{ + // Codec version + 0x00, 0x00, + // SetSubnetValidatorWeightTx Type ID + 0x00, 0x00, 0x00, 0x25, + // Network ID + 0x00, 0x00, 0x00, 0x0a, + // P-chain blockchain ID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Number of outputs + 0x00, 0x00, 0x00, 0x02, + // Outputs[0] + // AVAX assetID + 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, + 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, + 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, + 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, + // Stakeable locked output type ID + 0x00, 0x00, 0x00, 0x16, + // Locktime + 0x00, 0x00, 0x00, 0x00, 0x05, 0x39, 0x7f, 0xb1, + // secp256k1fx transfer output type ID + 0x00, 0x00, 0x00, 0x07, + // amount + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + // secp256k1fx output locktime + 0x00, 0x00, 0x00, 0x00, 0x00, 0xbc, 0x61, 0x4e, + // threshold + 0x00, 0x00, 0x00, 0x00, + // number of addresses + 0x00, 0x00, 0x00, 0x00, + // Outputs[1] + // custom asset ID + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + // Stakeable locked output type ID + 0x00, 0x00, 0x00, 0x16, + // Locktime + 0x00, 0x00, 0x00, 0x00, 0x34, 0x3e, 0xfc, 0xea, + // secp256k1fx transfer output type ID + 0x00, 0x00, 0x00, 0x07, + // amount + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + // secp256k1fx output locktime + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // threshold + 0x00, 0x00, 0x00, 0x01, + // number of addresses + 0x00, 0x00, 0x00, 0x01, + // address[0] + 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0x44, 0x55, 0x66, 0x77, + // number of inputs + 0x00, 0x00, 0x00, 0x03, + // inputs[0] + // TxID + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + // Tx output index + 0x00, 0x00, 0x00, 0x01, + // AVAX assetID + 0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a, + 0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78, + 0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf, + 0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff, + // secp256k1fx transfer input type ID + 0x00, 0x00, 0x00, 0x05, + // input amount = 1 Avax + 0x00, 0x00, 0x00, 0x00, 0x3b, 0x9a, 0xca, 0x00, + // number of signatures needed in input + 0x00, 0x00, 0x00, 0x02, + // index of first signer + 0x00, 0x00, 0x00, 0x02, + // index of second signer + 0x00, 0x00, 0x00, 0x05, + // inputs[1] + // TxID + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + // Tx output index + 0x00, 0x00, 0x00, 0x02, + // Custom asset ID + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + // Stakeable locked input type ID + 0x00, 0x00, 0x00, 0x15, + // Locktime + 0x00, 0x00, 0x00, 0x00, 0x34, 0x3e, 0xfc, 0xea, + // secp256k1fx transfer input type ID + 0x00, 0x00, 0x00, 0x05, + // input amount + 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + // number of signatures needed in input + 0x00, 0x00, 0x00, 0x01, + // index of signer + 0x00, 0x00, 0x00, 0x00, + // inputs[2] + // TxID + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, + // Tx output index + 0x00, 0x00, 0x00, 0x03, + // custom asset ID + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + 0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31, + // secp256k1fx transfer input type ID + 0x00, 0x00, 0x00, 0x05, + // input amount + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // number of signatures needed in input + 0x00, 0x00, 0x00, 0x00, + // length of memo + 0x00, 0x00, 0x00, 0x14, + // memo + 0xf0, 0x9f, 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c, + 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73, + 0x01, 0x23, 0x45, 0x21, + // length of message + 0x00, 0x00, 0x00, 0x07, + // message + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + } + require.Equal(expectedBytes, txBytes) + + ctx := snowtest.Context(t, constants.PlatformChainID) + unsignedTx.InitCtx(ctx) + + txJSON, err := json.MarshalIndent(unsignedTx, "", "\t") + require.NoError(err) + require.Equal( + // Normalize newlines for Windows + strings.ReplaceAll(string(setSubnetValidatorWeightTxJSON), "\r\n", "\n"), + string(txJSON), + ) +} + +func TestSetSubnetValidatorWeightTxSyntacticVerify(t *testing.T) { + ctx := snowtest.Context(t, ids.GenerateTestID()) + tests := []struct { + name string + tx *SetSubnetValidatorWeightTx + expectedErr error + }{ + { + name: "nil tx", + tx: nil, + expectedErr: ErrNilTx, + }, + { + name: "already verified", + // The tx includes invalid data to verify that a cached result is + // returned. + tx: &SetSubnetValidatorWeightTx{ + BaseTx: BaseTx{ + SyntacticallyVerified: true, + }, + }, + expectedErr: nil, + }, + { + name: "invalid BaseTx", + tx: &SetSubnetValidatorWeightTx{ + BaseTx: BaseTx{}, + }, + expectedErr: avax.ErrWrongNetworkID, + }, + { + name: "passes verification", + tx: &SetSubnetValidatorWeightTx{ + BaseTx: BaseTx{ + BaseTx: avax.BaseTx{ + NetworkID: ctx.NetworkID, + BlockchainID: ctx.ChainID, + }, + }, + }, + expectedErr: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + err := test.tx.SyntacticVerify(ctx) + require.ErrorIs(err, test.expectedErr) + if test.expectedErr != nil { + return + } + require.True(test.tx.SyntacticallyVerified) + }) + } +} diff --git a/vms/platformvm/txs/set_subnet_validator_weight_tx_test.json b/vms/platformvm/txs/set_subnet_validator_weight_tx_test.json new file mode 100644 index 000000000000..7f81afa155ac --- /dev/null +++ b/vms/platformvm/txs/set_subnet_validator_weight_tx_test.json @@ -0,0 +1,76 @@ +{ + "networkID": 10, + "blockchainID": "11111111111111111111111111111111LpoYY", + "outputs": [ + { + "assetID": "FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "locktime": 87654321, + "output": { + "addresses": [], + "amount": 1, + "locktime": 12345678, + "threshold": 0 + } + } + }, + { + "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "locktime": 876543210, + "output": { + "addresses": [ + "P-testing1g32kvaugnx4tk3z4vemc3xd2hdz92enhgrdu9n" + ], + "amount": 18446744073709551615, + "locktime": 0, + "threshold": 1 + } + } + } + ], + "inputs": [ + { + "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", + "outputIndex": 1, + "assetID": "FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "amount": 1000000000, + "signatureIndices": [ + 2, + 5 + ] + } + }, + { + "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", + "outputIndex": 2, + "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "locktime": 876543210, + "input": { + "amount": 17293822569102704639, + "signatureIndices": [ + 0 + ] + } + } + }, + { + "txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN", + "outputIndex": 3, + "assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "amount": 1152921504606846976, + "signatureIndices": [] + } + } + ], + "memo": "0xf09f98850a77656c6c2074686174277301234521", + "message": "0x6d657373616765" +} \ No newline at end of file From 6d04b1fba7804a4e415f27f12fb805b4e319457c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 8 Nov 2024 10:32:05 -0500 Subject: [PATCH 361/400] Add SetSubnetValidatorWeightTx builder tests --- wallet/chain/p/builder_test.go | 82 ++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/wallet/chain/p/builder_test.go b/wallet/chain/p/builder_test.go index bc03f30814e9..9a3cb1001401 100644 --- a/wallet/chain/p/builder_test.go +++ b/wallet/chain/p/builder_test.go @@ -849,6 +849,88 @@ func TestRegisterSubnetValidatorTx(t *testing.T) { } } +func TestSetSubnetValidatorWeightTx(t *testing.T) { + const ( + nonce = 1 + weight = 7905001371 + ) + var ( + validationID = ids.GenerateTestID() + chainID = ids.GenerateTestID() + address = utils.RandomBytes(20) + ) + + addressedCallPayload, err := message.NewSubnetValidatorWeight( + validationID, + nonce, + weight, + ) + require.NoError(t, err) + + addressedCall, err := payload.NewAddressedCall( + address, + addressedCallPayload.Bytes(), + ) + require.NoError(t, err) + + unsignedWarp, err := warp.NewUnsignedMessage( + constants.UnitTestID, + chainID, + addressedCall.Bytes(), + ) + require.NoError(t, err) + + sk, err := bls.NewSecretKey() + require.NoError(t, err) + + warp, err := warp.NewMessage( + unsignedWarp, + &warp.BitSetSignature{ + Signers: set.NewBits(0).Bytes(), + Signature: ([bls.SignatureLen]byte)( + bls.SignatureToBytes( + bls.Sign( + sk, + unsignedWarp.Bytes(), + ), + ), + ), + }, + ) + require.NoError(t, err) + + warpMessageBytes := warp.Bytes() + for _, e := range testEnvironmentPostEtna { + t.Run(e.name, func(t *testing.T) { + var ( + require = require.New(t) + chainUTXOs = utxotest.NewDeterministicChainUTXOs(t, map[ids.ID][]*avax.UTXO{ + constants.PlatformChainID: utxos, + }) + backend = wallet.NewBackend(e.context, chainUTXOs, nil) + builder = builder.New(set.Of(utxoAddr), e.context, backend) + ) + + utx, err := builder.NewSetSubnetValidatorWeightTx( + warpMessageBytes, + common.WithMemo(e.memo), + ) + require.NoError(err) + require.Equal(types.JSONByteSlice(warpMessageBytes), utx.Message) + require.Equal(types.JSONByteSlice(e.memo), utx.Memo) + requireFeeIsCorrect( + require, + e.feeCalculator, + utx, + &utx.BaseTx.BaseTx, + nil, + nil, + nil, + ) + }) + } +} + func makeTestUTXOs(utxosKey *secp256k1.PrivateKey) []*avax.UTXO { // Note: we avoid ids.GenerateTestNodeID here to make sure that UTXO IDs // won't change run by run. This simplifies checking what utxos are included From b71faf71d97a70ce4ccd69905338c11f273726c3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 8 Nov 2024 10:44:06 -0500 Subject: [PATCH 362/400] execution test nits --- vms/platformvm/txs/executor/standard_tx_executor_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index ba4f5853b7b6..c392e2dfe030 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -3028,7 +3028,7 @@ func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { expectedErr: errWarpMessageNotYetAllowed, }, { - name: "duplicate SoV", + name: "SoV previously registered", balance: 1, updateExecutor: func(e *standardTxExecutor) error { e.state.PutExpiry(state.ExpiryEntry{ @@ -3090,8 +3090,7 @@ func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { expectedErr: safemath.ErrOverflow, }, { - name: "duplicate SoV", - balance: 1, + name: "duplicate subnetID + nodeID pair", updateExecutor: func(e *standardTxExecutor) error { return e.state.PutSubnetOnlyValidator(state.SubnetOnlyValidator{ ValidationID: ids.GenerateTestID(), From fde96e7df4d9c7c6213043a246cc3ed3ac3e3c2d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 8 Nov 2024 13:02:52 -0500 Subject: [PATCH 363/400] Add SetSubnetValidatorWeightTx execution tests --- .../txs/executor/standard_tx_executor.go | 7 +- .../txs/executor/standard_tx_executor_test.go | 475 ++++++++++++++++++ 2 files changed, 479 insertions(+), 3 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 06d234353603..8be659f483c8 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -56,6 +56,7 @@ var ( errWarpMessageExpired = errors.New("warp message expired") errWarpMessageNotYetAllowed = errors.New("warp message not yet allowed") errWarpMessageAlreadyIssued = errors.New("warp message already issued") + errCouldNotLoadSoV = errors.New("could not load SoV") errWarpMessageContainsStaleNonce = errors.New("warp message contains stale nonce") errRemovingLastValidator = errors.New("attempting to remove the last SoV from a converted subnet") errStateCorruption = errors.New("state corruption") @@ -1012,7 +1013,7 @@ func (e *standardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat // Verify that the message contains a valid nonce for a current validator. sov, err := e.state.GetSubnetOnlyValidator(msg.ValidationID) if err != nil { - return err + return fmt.Errorf("%w: %w", errCouldNotLoadSoV, err) } if msg.Nonce < sov.MinNonce { return fmt.Errorf("%w %d must be at least %d", errWarpMessageContainsStaleNonce, msg.Nonce, sov.MinNonce) @@ -1031,7 +1032,7 @@ func (e *standardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat // Verify that we are not removing the last validator. weight, err := e.state.WeightOfSubnetOnlyValidators(sov.SubnetID) if err != nil { - return err + return fmt.Errorf("could not load SoV weights: %w", err) } if weight == sov.Weight { return errRemovingLastValidator @@ -1042,7 +1043,7 @@ func (e *standardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat if sov.EndAccumulatedFee != 0 { var remainingBalanceOwner message.PChainOwner if _, err := txs.Codec.Unmarshal(sov.RemainingBalanceOwner, &remainingBalanceOwner); err != nil { - return err + return fmt.Errorf("%w: remaining balance owner is malformed", errStateCorruption) } accruedFees := e.state.GetAccruedFees() diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index c392e2dfe030..569231d7e2c9 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -3208,6 +3208,481 @@ func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { } } +func TestStandardExecutorSetSubnetValidatorWeightTx(t *testing.T) { + var ( + fx = &secp256k1fx.Fx{} + vm = &secp256k1fx.TestVM{ + Log: logging.NoLog{}, + } + ) + require.NoError(t, fx.InitializeVM(vm)) + + var ( + ctx = snowtest.Context(t, constants.PlatformChainID) + defaultConfig = &config.Config{ + DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, + ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, + UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), + } + baseState = statetest.New(t, statetest.Config{ + Upgrades: defaultConfig.UpgradeConfig, + Context: ctx, + }) + wallet = txstest.NewWallet( + t, + ctx, + defaultConfig, + baseState, + secp256k1fx.NewKeychain(genesistest.DefaultFundedKeys...), + nil, // subnetIDs + nil, // chainIDs + ) + flowChecker = utxo.NewVerifier( + ctx, + &vm.Clk, + fx, + ) + + backend = &Backend{ + Config: defaultConfig, + Bootstrapped: utils.NewAtomic(true), + Fx: fx, + FlowChecker: flowChecker, + Ctx: ctx, + } + feeCalculator = state.PickFeeCalculator(defaultConfig, baseState) + ) + + // Create the initial state + diff, err := state.NewDiffOn(baseState) + require.NoError(t, err) + + // Create the subnet + createSubnetTx, err := wallet.IssueCreateSubnetTx( + &secp256k1fx.OutputOwners{}, + ) + require.NoError(t, err) + + // Execute the subnet creation + _, _, _, err = StandardTx( + backend, + feeCalculator, + createSubnetTx, + diff, + ) + require.NoError(t, err) + + // Create the subnet conversion + initialSK, err := bls.NewSecretKey() + require.NoError(t, err) + + const ( + initialWeight = 1 + initialBalance = units.Avax + ) + var ( + subnetID = createSubnetTx.ID() + chainID = ids.GenerateTestID() + address = utils.RandomBytes(32) + validator = &txs.ConvertSubnetValidator{ + NodeID: ids.GenerateTestNodeID().Bytes(), + Weight: initialWeight, + Balance: initialBalance, + Signer: *signer.NewProofOfPossession(initialSK), + // RemainingBalanceOwner and DeactivationOwner are initialized so + // that later reflect based equality checks pass. + RemainingBalanceOwner: message.PChainOwner{ + Threshold: 0, + Addresses: []ids.ShortID{}, + }, + DeactivationOwner: message.PChainOwner{ + Threshold: 0, + Addresses: []ids.ShortID{}, + }, + } + validationID = subnetID.Append(0) + ) + + convertSubnetTx, err := wallet.IssueConvertSubnetTx( + subnetID, + chainID, + address, + []*txs.ConvertSubnetValidator{ + validator, + }, + ) + require.NoError(t, err) + + // Execute the subnet conversion + _, _, _, err = StandardTx( + backend, + feeCalculator, + convertSubnetTx, + diff, + ) + require.NoError(t, err) + require.NoError(t, diff.Apply(baseState)) + require.NoError(t, baseState.Commit()) + + initialSoV, err := baseState.GetSubnetOnlyValidator(validationID) + require.NoError(t, err) + + // Create the Warp message + const ( + nonce = 1 + weight = initialWeight + 1 + ) + unsignedIncreaseWeightWarpMessage := must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + address, + must[*message.SubnetValidatorWeight](t)(message.NewSubnetValidatorWeight( + validationID, + nonce, + weight, + )).Bytes(), + )).Bytes(), + )) + warpSignature := &warp.BitSetSignature{ + Signers: set.NewBits(0).Bytes(), + Signature: ([bls.SignatureLen]byte)(bls.SignatureToBytes( + bls.Sign( + initialSK, + unsignedIncreaseWeightWarpMessage.Bytes(), + ), + )), + } + increaseWeightWarpMessage := must[*warp.Message](t)(warp.NewMessage( + unsignedIncreaseWeightWarpMessage, + warpSignature, + )) + removeValidatorWarpMessage := must[*warp.Message](t)(warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + address, + must[*message.SubnetValidatorWeight](t)(message.NewSubnetValidatorWeight( + validationID, + nonce, + 0, + )).Bytes(), + )).Bytes(), + )), + warpSignature, + )) + + increaseL1Weight := func(weight uint64) func(*standardTxExecutor) error { + return func(e *standardTxExecutor) error { + sov := initialSoV + sov.ValidationID = ids.GenerateTestID() + sov.NodeID = ids.GenerateTestNodeID() + sov.Weight = weight + return e.state.PutSubnetOnlyValidator(sov) + } + } + + tests := []struct { + name string + message []byte + builderOptions []common.Option + updateExecutor func(*standardTxExecutor) error + expectedNonce uint64 + expectedWeight uint64 + expectedRemainingFundsUTXO *avax.UTXO + expectedErr error + }{ + { + name: "invalid prior to E-Upgrade", + 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) error { + e.backend.Ctx = snowtest.Context(t, ids.GenerateTestID()) + return nil + }, + expectedErr: avax.ErrWrongChainID, + }, + { + name: "invalid memo length", + builderOptions: []common.Option{ + common.WithMemo([]byte("memo!")), + }, + expectedErr: avax.ErrMemoTooLarge, + }, + { + name: "invalid fee calculation", + updateExecutor: func(e *standardTxExecutor) error { + e.feeCalculator = txfee.NewStaticCalculator(e.backend.Config.StaticFeeConfig) + return nil + }, + expectedErr: txfee.ErrUnsupportedTx, + }, + { + name: "insufficient fee", + updateExecutor: func(e *standardTxExecutor) error { + e.feeCalculator = txfee.NewDynamicCalculator( + e.backend.Config.DynamicFeeConfig.Weights, + 100*genesis.LocalParams.DynamicFeeConfig.MinPrice, + ) + return nil + }, + expectedErr: utxo.ErrInsufficientUnlockedFunds, + }, + { + name: "invalid warp message", + message: []byte{}, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "invalid warp payload", + message: must[*warp.Message](t)(warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.Hash](t)(payload.NewHash(ids.Empty)).Bytes(), + )), + warpSignature, + )).Bytes(), + expectedErr: payload.ErrWrongType, + }, + { + name: "invalid addressed call", + message: must[*warp.Message](t)(warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + address, + must[*message.SubnetConversion](t)(message.NewSubnetConversion(ids.Empty)).Bytes(), + )).Bytes(), + )), + warpSignature, + )).Bytes(), + expectedErr: message.ErrWrongType, + }, + { + name: "invalid addressed call payload", + message: must[*warp.Message](t)(warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + address, + must[*message.SubnetValidatorWeight](t)(message.NewSubnetValidatorWeight( + validationID, + math.MaxUint64, + 1, + )).Bytes(), + )).Bytes(), + )), + warpSignature, + )).Bytes(), + expectedErr: message.ErrNonceReservedForRemoval, + }, + { + name: "SoV not found", + message: must[*warp.Message](t)(warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + address, + must[*message.SubnetValidatorWeight](t)(message.NewSubnetValidatorWeight( + ids.GenerateTestID(), // invalid initialValidationID + nonce, + weight, + )).Bytes(), + )).Bytes(), + )), + warpSignature, + )).Bytes(), + expectedErr: errCouldNotLoadSoV, + }, + { + name: "nonce too low", + updateExecutor: func(e *standardTxExecutor) error { + sov := initialSoV + sov.MinNonce = nonce + 1 + return e.state.PutSubnetOnlyValidator(sov) + }, + expectedErr: errWarpMessageContainsStaleNonce, + }, + { + name: "invalid source chain", + updateExecutor: func(e *standardTxExecutor) error { + e.state.SetSubnetConversion(subnetID, state.SubnetConversion{}) + return nil + }, + expectedErr: errWrongWarpMessageSourceChainID, + }, + { + name: "invalid source address", + updateExecutor: func(e *standardTxExecutor) error { + e.state.SetSubnetConversion(subnetID, state.SubnetConversion{ + ChainID: chainID, + }) + return nil + }, + expectedErr: errWrongWarpMessageSourceAddress, + }, + { + name: "remove last validator", + message: removeValidatorWarpMessage.Bytes(), + expectedErr: errRemovingLastValidator, + }, + { + name: "remove deactivated validator", + message: removeValidatorWarpMessage.Bytes(), + updateExecutor: func(e *standardTxExecutor) error { + // Add another validator to allow removal + if err := increaseL1Weight(1)(e); err != nil { + return err + } + + sov := initialSoV + sov.EndAccumulatedFee = 0 // Deactivate the validator + return e.state.PutSubnetOnlyValidator(sov) + }, + }, + { + name: "should have been previously deactivated", + message: removeValidatorWarpMessage.Bytes(), + updateExecutor: func(e *standardTxExecutor) error { + e.state.SetAccruedFees(initialSoV.EndAccumulatedFee) + return increaseL1Weight(1)(e) + }, + expectedErr: errStateCorruption, + }, + { + name: "remove active validator", + message: removeValidatorWarpMessage.Bytes(), + updateExecutor: increaseL1Weight(1), + expectedRemainingFundsUTXO: &avax.UTXO{ + Asset: avax.Asset{ + ID: ctx.AVAXAssetID, + }, + Out: &secp256k1fx.TransferOutput{ + Amt: initialBalance, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: validator.RemainingBalanceOwner.Threshold, + Addrs: validator.RemainingBalanceOwner.Addresses, + }, + }, + }, + }, + { + name: "L1 weight overflow", + updateExecutor: increaseL1Weight(math.MaxUint64 - initialWeight), + expectedErr: safemath.ErrOverflow, + }, + { + name: "update validator", + expectedNonce: nonce + 1, + expectedWeight: weight, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + // Create the SetSubnetValidatorWeightTx + wallet := txstest.NewWallet( + t, + ctx, + defaultConfig, + baseState, + secp256k1fx.NewKeychain(genesistest.DefaultFundedKeys...), + nil, // subnetIDs + nil, // chainIDs + ) + + message := test.message + if message == nil { + message = increaseWeightWarpMessage.Bytes() + } + setSubnetValidatorWeightTx, err := wallet.IssueSetSubnetValidatorWeightTx( + message, + test.builderOptions..., + ) + require.NoError(err) + + diff, err := state.NewDiffOn(baseState) + require.NoError(err) + + executor := &standardTxExecutor{ + backend: &Backend{ + Config: defaultConfig, + Bootstrapped: utils.NewAtomic(true), + Fx: fx, + FlowChecker: flowChecker, + Ctx: ctx, + }, + feeCalculator: state.PickFeeCalculator(defaultConfig, baseState), + tx: setSubnetValidatorWeightTx, + state: diff, + } + if test.updateExecutor != nil { + require.NoError(test.updateExecutor(executor)) + } + + err = setSubnetValidatorWeightTx.Unsigned.Visit(executor) + require.ErrorIs(err, test.expectedErr) + if err != nil { + return + } + + for utxoID := range setSubnetValidatorWeightTx.InputIDs() { + _, err := diff.GetUTXO(utxoID) + require.ErrorIs(err, database.ErrNotFound) + } + + baseTxOutputUTXOs := setSubnetValidatorWeightTx.UTXOs() + for _, expectedUTXO := range baseTxOutputUTXOs { + utxoID := expectedUTXO.InputID() + utxo, err := diff.GetUTXO(utxoID) + require.NoError(err) + require.Equal(expectedUTXO, utxo) + } + + sov, err := diff.GetSubnetOnlyValidator(validationID) + if test.expectedWeight != 0 { + require.NoError(err) + + expectedSoV := initialSoV + expectedSoV.MinNonce = test.expectedNonce + expectedSoV.Weight = test.expectedWeight + require.Equal(expectedSoV, sov) + return + } + require.ErrorIs(err, database.ErrNotFound) + + utxoID := avax.UTXOID{ + TxID: setSubnetValidatorWeightTx.ID(), + OutputIndex: uint32(len(baseTxOutputUTXOs)), + } + inputID := utxoID.InputID() + utxo, err := diff.GetUTXO(inputID) + if test.expectedRemainingFundsUTXO == nil { + require.ErrorIs(err, database.ErrNotFound) + return + } + require.NoError(err) + + test.expectedRemainingFundsUTXO.UTXOID = utxoID + require.Equal(test.expectedRemainingFundsUTXO, utxo) + }) + } +} + func must[T any](t require.TestingT) func(T, error) T { return func(val T, err error) T { require.NoError(t, err) From c975b679124e0c3e2bf9387788d4841ab837380c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 8 Nov 2024 13:04:40 -0500 Subject: [PATCH 364/400] nit --- vms/platformvm/txs/executor/standard_tx_executor_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 569231d7e2c9..3acad24eb5e0 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -3327,7 +3327,7 @@ func TestStandardExecutorSetSubnetValidatorWeightTx(t *testing.T) { initialSoV, err := baseState.GetSubnetOnlyValidator(validationID) require.NoError(t, err) - // Create the Warp message + // Create the Warp messages const ( nonce = 1 weight = initialWeight + 1 From ddd97e3612cbaf2ccecca8d3b692cdb3faa86ed6 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 8 Nov 2024 13:08:24 -0500 Subject: [PATCH 365/400] Add overflow case --- .../txs/executor/standard_tx_executor_test.go | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 3acad24eb5e0..970c97121f82 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -3553,6 +3553,34 @@ func TestStandardExecutorSetSubnetValidatorWeightTx(t *testing.T) { return e.state.PutSubnetOnlyValidator(sov) }, }, + { + name: "remove deactivated validator with nonce overflow", + message: must[*warp.Message](t)(warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + ctx.NetworkID, + chainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + address, + must[*message.SubnetValidatorWeight](t)(message.NewSubnetValidatorWeight( + validationID, + math.MaxUint64, + 0, + )).Bytes(), + )).Bytes(), + )), + warpSignature, + )).Bytes(), + updateExecutor: func(e *standardTxExecutor) error { + // Add another validator to allow removal + if err := increaseL1Weight(1)(e); err != nil { + return err + } + + sov := initialSoV + sov.EndAccumulatedFee = 0 // Deactivate the validator + return e.state.PutSubnetOnlyValidator(sov) + }, + }, { name: "should have been previously deactivated", message: removeValidatorWarpMessage.Bytes(), From abae44142ea346faa0623f093fab27b8d6abddc4 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 8 Nov 2024 15:37:49 -0500 Subject: [PATCH 366/400] nits from PR review --- vms/platformvm/block/builder/builder.go | 14 +++++++------- vms/platformvm/block/executor/block.go | 25 ++++++++++++------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/vms/platformvm/block/builder/builder.go b/vms/platformvm/block/builder/builder.go index 86fe2ab3beaa..410d3285a9e7 100644 --- a/vms/platformvm/block/builder/builder.go +++ b/vms/platformvm/block/builder/builder.go @@ -211,7 +211,12 @@ func (b *builder) ShutdownBlockTimer() { } func (b *builder) BuildBlock(ctx context.Context) (snowman.Block, error) { - return b.BuildBlockWithContext(ctx, nil) + return b.BuildBlockWithContext( + ctx, + &smblock.Context{ + PChainHeight: 0, + }, + ) } func (b *builder) BuildBlockWithContext( @@ -245,11 +250,6 @@ func (b *builder) BuildBlockWithContext( 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, @@ -258,7 +258,7 @@ func (b *builder) BuildBlockWithContext( timestamp, timeWasCapped, preferredState, - pChainHeight, + blockContext.PChainHeight, ) if err != nil { return nil, err diff --git a/vms/platformvm/block/executor/block.go b/vms/platformvm/block/executor/block.go index 3a8f459915fd..0c3cffc89954 100644 --- a/vms/platformvm/block/executor/block.go +++ b/vms/platformvm/block/executor/block.go @@ -30,29 +30,23 @@ func (*Block) ShouldVerifyWithContext(context.Context) (bool, error) { } func (b *Block) VerifyWithContext(ctx context.Context, blockContext *smblock.Context) error { - var pChainHeight uint64 - if blockContext != nil { - pChainHeight = blockContext.PChainHeight - } - blkID := b.ID() if blkState, ok := b.manager.blkIDToState[blkID]; ok { - if !blkState.verifiedHeights.Contains(pChainHeight) { + if !blkState.verifiedHeights.Contains(blockContext.PChainHeight) { // Only the validity of warp messages need to be verified because - // this block has already passed verification with a different - // height. + // this block was already executed with a different block context. err := VerifyWarpMessages( ctx, b.manager.ctx.NetworkID, b.manager.ctx.ValidatorState, - pChainHeight, + blockContext.PChainHeight, b, ) if err != nil { return err } - blkState.verifiedHeights.Add(pChainHeight) + blkState.verifiedHeights.Add(blockContext.PChainHeight) } return nil // This block has already been executed. @@ -63,7 +57,7 @@ func (b *Block) VerifyWithContext(ctx context.Context, blockContext *smblock.Con ctx, b.manager.ctx.NetworkID, b.manager.ctx.ValidatorState, - pChainHeight, + blockContext.PChainHeight, b, ) if err != nil { @@ -75,12 +69,17 @@ func (b *Block) VerifyWithContext(ctx context.Context, blockContext *smblock.Con return b.Visit(&verifier{ backend: b.manager.backend, txExecutorBackend: b.manager.txExecutorBackend, - pChainHeight: pChainHeight, + pChainHeight: blockContext.PChainHeight, }) } func (b *Block) Verify(ctx context.Context) error { - return b.VerifyWithContext(ctx, nil) + return b.VerifyWithContext( + ctx, + &smblock.Context{ + PChainHeight: 0, + }, + ) } func (b *Block) Accept(context.Context) error { From b3e1095d11d1b86084922671be997cdec5f203d3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 8 Nov 2024 15:43:42 -0500 Subject: [PATCH 367/400] unexport visitor --- vms/platformvm/txs/executor/warp_verifier.go | 68 ++++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/vms/platformvm/txs/executor/warp_verifier.go b/vms/platformvm/txs/executor/warp_verifier.go index 5c7430629f32..731cac7b3827 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{ - Context: ctx, - NetworkID: networkID, - ValidatorState: validatorState, - PChainHeight: pChainHeight, + return tx.Visit(&warpVerifier{ + context: ctx, + networkID: networkID, + validatorState: validatorState, + pChainHeight: pChainHeight, }) } -type WarpVerifier struct { - Context 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.Context, + w.context, &msg.UnsignedMessage, - w.NetworkID, - w.ValidatorState, - w.PChainHeight, + w.networkID, + w.validatorState, + w.pChainHeight, WarpQuorumNumerator, WarpQuorumDenominator, ) From 5f86e1a5511caa5defddcabbba577c7b970a5930 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Fri, 8 Nov 2024 22:33:59 -0500 Subject: [PATCH 368/400] add tx warp verifier tests --- .../txs/executor/warp_verifier_test.go | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 vms/platformvm/txs/executor/warp_verifier_test.go diff --git a/vms/platformvm/txs/executor/warp_verifier_test.go b/vms/platformvm/txs/executor/warp_verifier_test.go new file mode 100644 index 000000000000..fd925f4f7825 --- /dev/null +++ b/vms/platformvm/txs/executor/warp_verifier_test.go @@ -0,0 +1,206 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package executor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/snow/validators/validatorstest" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" +) + +func TestVerifyWarpMessages(t *testing.T) { + var ( + subnetID = ids.GenerateTestID() + chainID = ids.GenerateTestID() + newValidator = func() (*bls.SecretKey, *validators.GetValidatorOutput) { + sk, err := bls.NewSecretKey() + require.NoError(t, err) + + return sk, &validators.GetValidatorOutput{ + NodeID: ids.GenerateTestNodeID(), + PublicKey: bls.PublicFromSecretKey(sk), + Weight: 1, + } + } + sk0, vdr0 = newValidator() + sk1, vdr1 = newValidator() + vdrs = map[ids.NodeID]*validators.GetValidatorOutput{ + vdr0.NodeID: vdr0, + vdr1.NodeID: vdr1, + } + state = &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 vdrs, nil + }, + } + ) + + validUnsignedWarpMessage, err := warp.NewUnsignedMessage( + constants.UnitTestID, + chainID, + nil, + ) + require.NoError(t, err) + + var ( + sig0 = bls.Sign(sk0, validUnsignedWarpMessage.Bytes()) + sig1 = bls.Sign(sk1, validUnsignedWarpMessage.Bytes()) + ) + sig, err := bls.AggregateSignatures([]*bls.Signature{sig0, sig1}) + require.NoError(t, err) + + warpSignature := &warp.BitSetSignature{ + Signers: set.NewBits(0, 1).Bytes(), + Signature: [bls.SignatureLen]byte(bls.SignatureToBytes(sig)), + } + validWarpMessage, err := warp.NewMessage( + validUnsignedWarpMessage, + warpSignature, + ) + require.NoError(t, err) + + invalidWarpMessage, err := warp.NewMessage( + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + constants.UnitTestID+1, + chainID, + nil, + )), + warpSignature, + ) + require.NoError(t, err) + + tests := []struct { + name string + tx txs.UnsignedTx + expectedErr error + }{ + { + name: "AddValidatorTx", + tx: &txs.AddValidatorTx{}, + }, + { + name: "AddSubnetValidatorTx", + tx: &txs.AddSubnetValidatorTx{}, + }, + { + name: "AddDelegatorTx", + tx: &txs.AddDelegatorTx{}, + }, + { + name: "CreateChainTx", + tx: &txs.CreateChainTx{}, + }, + { + name: "CreateSubnetTx", + tx: &txs.CreateSubnetTx{}, + }, + { + name: "ImportTx", + tx: &txs.ImportTx{}, + }, + { + name: "ExportTx", + tx: &txs.ExportTx{}, + }, + { + name: "AdvanceTimeTx", + tx: &txs.AdvanceTimeTx{}, + }, + { + name: "RewardValidatorTx", + tx: &txs.RewardValidatorTx{}, + }, + { + name: "RemoveSubnetValidatorTx", + tx: &txs.RemoveSubnetValidatorTx{}, + }, + { + name: "TransformSubnetTx", + tx: &txs.TransformSubnetTx{}, + }, + { + name: "AddPermissionlessValidatorTx", + tx: &txs.AddPermissionlessValidatorTx{}, + }, + { + name: "AddPermissionlessDelegatorTx", + tx: &txs.AddPermissionlessDelegatorTx{}, + }, + { + name: "TransferSubnetOwnershipTx", + tx: &txs.TransferSubnetOwnershipTx{}, + }, + { + name: "BaseTx", + tx: &txs.BaseTx{}, + }, + { + name: "ConvertSubnetTx", + tx: &txs.ConvertSubnetTx{}, + }, + { + name: "RegisterSubnetValidatorTx with unparsable message", + tx: &txs.RegisterSubnetValidatorTx{}, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "RegisterSubnetValidatorTx with invalid message", + tx: &txs.RegisterSubnetValidatorTx{ + Message: invalidWarpMessage.Bytes(), + }, + expectedErr: warp.ErrWrongNetworkID, + }, + { + name: "RegisterSubnetValidatorTx with valid message", + tx: &txs.RegisterSubnetValidatorTx{ + Message: validWarpMessage.Bytes(), + }, + }, + { + name: "SetSubnetValidatorWeightTx with unparsable message", + tx: &txs.SetSubnetValidatorWeightTx{}, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "SetSubnetValidatorWeightTx with invalid message", + tx: &txs.SetSubnetValidatorWeightTx{ + Message: invalidWarpMessage.Bytes(), + }, + expectedErr: warp.ErrWrongNetworkID, + }, + { + name: "SetSubnetValidatorWeightTx with valid message", + tx: &txs.SetSubnetValidatorWeightTx{ + Message: validWarpMessage.Bytes(), + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := VerifyWarpMessages( + context.Background(), + constants.UnitTestID, + state, + 0, + test.tx, + ) + require.Equal(t, test.expectedErr, err) + }) + } +} From 4c9f3765cf1bdc5d8b50ad1224759432b30d3a1b Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 9 Nov 2024 11:27:26 -0500 Subject: [PATCH 369/400] Add block warp verifier --- .../block/executor/warp_verifier_test.go | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 vms/platformvm/block/executor/warp_verifier_test.go diff --git a/vms/platformvm/block/executor/warp_verifier_test.go b/vms/platformvm/block/executor/warp_verifier_test.go new file mode 100644 index 000000000000..c05ffc9057eb --- /dev/null +++ b/vms/platformvm/block/executor/warp_verifier_test.go @@ -0,0 +1,155 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package executor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/vms/platformvm/block" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" +) + +func TestVerifyWarpMessages(t *testing.T) { + var ( + validTx = &txs.Tx{ + Unsigned: &txs.BaseTx{}, + } + invalidTx = &txs.Tx{ + Unsigned: &txs.RegisterSubnetValidatorTx{}, + } + ) + + tests := []struct { + name string + block block.Block + expectedErr error + }{ + { + name: "BanffAbortBlock", + block: &block.BanffAbortBlock{}, + }, + { + name: "BanffCommitBlock", + block: &block.BanffCommitBlock{}, + }, + { + name: "BanffProposalBlock with invalid standard tx", + block: &block.BanffProposalBlock{ + Transactions: []*txs.Tx{ + invalidTx, + }, + ApricotProposalBlock: block.ApricotProposalBlock{ + Tx: validTx, + }, + }, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "BanffProposalBlock with invalid proposal tx", + block: &block.BanffProposalBlock{ + ApricotProposalBlock: block.ApricotProposalBlock{ + Tx: invalidTx, + }, + }, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "BanffProposalBlock with valid proposal tx", + block: &block.BanffProposalBlock{ + ApricotProposalBlock: block.ApricotProposalBlock{ + Tx: validTx, + }, + }, + }, + { + name: "BanffStandardBlock with invalid tx", + block: &block.BanffStandardBlock{ + ApricotStandardBlock: block.ApricotStandardBlock{ + Transactions: []*txs.Tx{ + invalidTx, + }, + }, + }, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "BanffStandardBlock with valid tx", + block: &block.BanffStandardBlock{ + ApricotStandardBlock: block.ApricotStandardBlock{ + Transactions: []*txs.Tx{ + validTx, + }, + }, + }, + }, + { + name: "ApricotAbortBlock", + block: &block.ApricotAbortBlock{}, + }, + { + name: "ApricotCommitBlock", + block: &block.ApricotCommitBlock{}, + }, + { + name: "ApricotProposalBlock with invalid proposal tx", + block: &block.ApricotProposalBlock{ + Tx: invalidTx, + }, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "ApricotProposalBlock with valid proposal tx", + block: &block.ApricotProposalBlock{ + Tx: validTx, + }, + }, + { + name: "ApricotStandardBlock with invalid tx", + block: &block.ApricotStandardBlock{ + Transactions: []*txs.Tx{ + invalidTx, + }, + }, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "ApricotStandardBlock with valid tx", + block: &block.ApricotStandardBlock{ + Transactions: []*txs.Tx{ + validTx, + }, + }, + }, + { + name: "ApricotAtomicBlock with invalid proposal tx", + block: &block.ApricotAtomicBlock{ + Tx: invalidTx, + }, + expectedErr: codec.ErrCantUnpackVersion, + }, + { + name: "ApricotAtomicBlock with valid proposal tx", + block: &block.ApricotAtomicBlock{ + Tx: validTx, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := VerifyWarpMessages( + context.Background(), + constants.UnitTestID, + nil, + 0, + test.block, + ) + require.Equal(t, test.expectedErr, err) + }) + } +} From b44a5ebb99ec040b4aa8931672c2e187a66ff989 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 9 Nov 2024 11:30:56 -0500 Subject: [PATCH 370/400] Reduce diff --- .../block/executor/warp_verifier.go | 77 +++---------------- 1 file changed, 9 insertions(+), 68 deletions(-) diff --git a/vms/platformvm/block/executor/warp_verifier.go b/vms/platformvm/block/executor/warp_verifier.go index 9587420da368..30e16c23e376 100644 --- a/vms/platformvm/block/executor/warp_verifier.go +++ b/vms/platformvm/block/executor/warp_verifier.go @@ -8,12 +8,9 @@ import ( "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( @@ -23,73 +20,17 @@ func VerifyWarpMessages( 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 { + for _, tx := range b.Txs() { + err := executor.VerifyWarpMessages( + ctx, + networkID, + validatorState, + pChainHeight, + tx.Unsigned, + ) + if 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, - ) -} From 071a4ee0a64fed3acaf32e6bb110df71864f7a4f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sat, 9 Nov 2024 16:37:24 -0500 Subject: [PATCH 371/400] Start adding signer tests --- vms/platformvm/network/warp_test.go | 238 ++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 vms/platformvm/network/warp_test.go diff --git a/vms/platformvm/network/warp_test.go b/vms/platformvm/network/warp_test.go new file mode 100644 index 000000000000..dd364cdc1c98 --- /dev/null +++ b/vms/platformvm/network/warp_test.go @@ -0,0 +1,238 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package network + +import ( + "context" + "sync" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/exp/rand" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/engine/common" + "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/vms/platformvm/state" + "github.com/ava-labs/avalanchego/vms/platformvm/state/statetest" + "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" +) + +func TestSignatureRequestVerify(t *testing.T) { + tests := []struct { + name string + payload []byte + expectedErr *common.AppError + }{ + { + name: "failed to parse warp addressed call", + payload: nil, + expectedErr: &common.AppError{ + Code: ErrFailedToParseWarpAddressedCall, + Message: "failed to parse warp addressed call: couldn't unpack codec version", + }, + }, + { + name: "warp addressed call has source address", + payload: must[*payload.AddressedCall](t)(payload.NewAddressedCall( + []byte{1}, + nil, + )).Bytes(), + expectedErr: &common.AppError{ + Code: ErrWarpAddressedCallHasSourceAddress, + Message: "source address should be empty", + }, + }, + { + name: "failed to parse warp addressed call payload", + payload: must[*payload.AddressedCall](t)(payload.NewAddressedCall( + nil, + nil, + )).Bytes(), + expectedErr: &common.AppError{ + Code: ErrFailedToParseWarpAddressedCallPayload, + Message: "failed to parse warp addressed call payload: couldn't unpack codec version", + }, + }, + { + name: "unsupported warp addressed call payload type", + payload: must[*payload.AddressedCall](t)(payload.NewAddressedCall( + nil, + must[*message.RegisterSubnetValidator](t)(message.NewRegisterSubnetValidator( + ids.GenerateTestID(), + ids.GenerateTestNodeID(), + [bls.PublicKeyLen]byte{}, + rand.Uint64(), + message.PChainOwner{}, + message.PChainOwner{}, + rand.Uint64(), + )).Bytes(), + )).Bytes(), + expectedErr: &common.AppError{ + Code: ErrUnsupportedWarpAddressedCallPayloadType, + Message: "unsupported warp addressed call payload type: *message.RegisterSubnetValidator", + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + s := signatureRequestVerifier{} + err := s.Verify( + context.Background(), + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + constants.UnitTestID, + constants.PlatformChainID, + test.payload, + )), + nil, + ) + require.Equal(t, test.expectedErr, err) + }) + } +} + +func TestSignatureRequestVerifySubnetConversion(t *testing.T) { + var ( + subnetID = ids.GenerateTestID() + conversion = state.SubnetConversion{ + ConversionID: ids.ID{1, 2, 3, 4, 5, 6, 7, 8}, + ChainID: ids.GenerateTestID(), + Addr: utils.RandomBytes(20), + } + state = statetest.New(t, statetest.Config{}) + s = signatureRequestVerifier{ + stateLock: &sync.Mutex{}, + state: state, + } + ) + + state.SetSubnetConversion(subnetID, conversion) + + tests := []struct { + name string + subnetID []byte + conversionID ids.ID + expectedErr *common.AppError + }{ + { + name: "failed to parse justification", + subnetID: nil, + expectedErr: &common.AppError{ + Code: ErrFailedToParseJustification, + Message: "failed to parse justification: invalid hash length: expected 32 bytes but got 0", + }, + }, + { + name: "conversion does not exist", + subnetID: ids.Empty[:], + expectedErr: &common.AppError{ + Code: ErrConversionDoesNotExist, + Message: `subnet "11111111111111111111111111111111LpoYY" has not been converted`, + }, + }, + { + name: "mismatched conversionID", + subnetID: subnetID[:], + expectedErr: &common.AppError{ + Code: ErrMismatchedConversionID, + Message: `provided conversionID "11111111111111111111111111111111LpoYY" != expected conversionID "SkB92YpWm4Jdy1AQvv4wMsUNbcoYBVZRqKkdz5yByq1bfdik"`, + }, + }, + { + name: "valid", + subnetID: subnetID[:], + conversionID: conversion.ConversionID, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := s.Verify( + context.Background(), + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + constants.UnitTestID, + constants.PlatformChainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + nil, + must[*message.SubnetConversion](t)(message.NewSubnetConversion( + test.conversionID, + )).Bytes(), + )).Bytes(), + )), + test.subnetID, + ) + require.Equal(t, test.expectedErr, err) + }) + } +} + +func TestSignatureRequestVerifySubnetValidatorRegistrationRegistered(t *testing.T) { + sk, err := bls.NewSecretKey() + require.NoError(t, err) + + var ( + sov = state.SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: bls.PublicKeyToUncompressedBytes(bls.PublicFromSecretKey(sk)), + Weight: 1, + } + state = statetest.New(t, statetest.Config{}) + s = signatureRequestVerifier{ + stateLock: &sync.Mutex{}, + state: state, + } + ) + + require.NoError(t, state.PutSubnetOnlyValidator(sov)) + + tests := []struct { + name string + validationID ids.ID + expectedErr *common.AppError + }{ + { + name: "validation does not exist", + expectedErr: &common.AppError{ + Code: ErrValidationDoesNotExist, + Message: `validation "11111111111111111111111111111111LpoYY" does not exist`, + }, + }, + { + name: "validation exists", + validationID: sov.ValidationID, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := s.Verify( + context.Background(), + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + constants.UnitTestID, + constants.PlatformChainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + nil, + must[*message.SubnetValidatorRegistration](t)(message.NewSubnetValidatorRegistration( + test.validationID, + true, + )).Bytes(), + )).Bytes(), + )), + nil, + ) + require.Equal(t, test.expectedErr, err) + }) + } +} + +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 5450210bc2319b6146d45bbcbf0213b2ee2d42a1 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 10 Nov 2024 09:36:30 -0500 Subject: [PATCH 372/400] Add more tests --- vms/platformvm/network/warp_test.go | 291 ++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) diff --git a/vms/platformvm/network/warp_test.go b/vms/platformvm/network/warp_test.go index dd364cdc1c98..948e57916bcb 100644 --- a/vms/platformvm/network/warp_test.go +++ b/vms/platformvm/network/warp_test.go @@ -5,17 +5,22 @@ package network import ( "context" + "encoding/hex" + "strings" "sync" "testing" "github.com/stretchr/testify/require" "golang.org/x/exp/rand" + "google.golang.org/protobuf/proto" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/proto/pb/platformvm" "github.com/ava-labs/avalanchego/snow/engine/common" "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/vms/platformvm/genesis/genesistest" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/state/statetest" "github.com/ava-labs/avalanchego/vms/platformvm/warp" @@ -230,6 +235,292 @@ func TestSignatureRequestVerifySubnetValidatorRegistrationRegistered(t *testing. } } +func TestSignatureRequestVerifySubnetValidatorRegistrationNotRegistered(t *testing.T) { + skBytes, err := hex.DecodeString("36a33c536d283dfa599d0a70839c67ded6c954e346c5e8e5b4794e2299907887") + require.NoError(t, err) + + sk, err := bls.SecretKeyFromBytes(skBytes) + require.NoError(t, err) + + var ( + subnetID = ids.ID{3} + nodeID0 = ids.NodeID{4} + nodeID1 = ids.NodeID{5} + nodeID2 = ids.NodeID{6} + nodeID3 = ids.NodeID{6} + pk = bls.PublicFromSecretKey(sk) + expiry = genesistest.DefaultValidatorStartTimeUnix + 1 + weight uint64 = 1 + ) + + registerSubnetValidatorToRegister, err := message.NewRegisterSubnetValidator( + subnetID, + nodeID0, + [bls.PublicKeyLen]byte(bls.PublicKeyToCompressedBytes(pk)), + expiry, + message.PChainOwner{}, + message.PChainOwner{}, + weight, + ) + require.NoError(t, err) + + registerSubnetValidatorNotToRegister, err := message.NewRegisterSubnetValidator( + subnetID, + nodeID1, + [bls.PublicKeyLen]byte(bls.PublicKeyToCompressedBytes(pk)), + expiry, + message.PChainOwner{}, + message.PChainOwner{}, + weight, + ) + require.NoError(t, err) + + registerSubnetValidatorExpired, err := message.NewRegisterSubnetValidator( + subnetID, + nodeID2, + [bls.PublicKeyLen]byte(bls.PublicKeyToCompressedBytes(pk)), + genesistest.DefaultValidatorStartTimeUnix, + message.PChainOwner{}, + message.PChainOwner{}, + weight, + ) + require.NoError(t, err) + + registerSubnetValidatorToMarkExpired, err := message.NewRegisterSubnetValidator( + subnetID, + nodeID3, + [bls.PublicKeyLen]byte(bls.PublicKeyToCompressedBytes(pk)), + expiry, + message.PChainOwner{}, + message.PChainOwner{}, + weight, + ) + require.NoError(t, err) + + var ( + registerSubnetValidatorValidationID = registerSubnetValidatorToRegister.ValidationID() + registerSubnetValidatorSoV = state.SubnetOnlyValidator{ + ValidationID: registerSubnetValidatorValidationID, + SubnetID: registerSubnetValidatorToRegister.SubnetID, + NodeID: ids.NodeID(registerSubnetValidatorToRegister.NodeID), + PublicKey: bls.PublicKeyToUncompressedBytes(pk), + Weight: registerSubnetValidatorToRegister.Weight, + } + convertSubnetValidatorValidationID = registerSubnetValidatorToRegister.SubnetID.Append(0) + convertSubnetValidatorSoV = state.SubnetOnlyValidator{ + ValidationID: convertSubnetValidatorValidationID, + SubnetID: registerSubnetValidatorToRegister.SubnetID, + NodeID: ids.GenerateTestNodeID(), + PublicKey: bls.PublicKeyToUncompressedBytes(pk), + Weight: registerSubnetValidatorToRegister.Weight, + } + expiryEntry = state.ExpiryEntry{ + Timestamp: expiry, + ValidationID: registerSubnetValidatorToMarkExpired.ValidationID(), + } + + state = statetest.New(t, statetest.Config{}) + s = signatureRequestVerifier{ + stateLock: &sync.Mutex{}, + state: state, + } + ) + + require.NoError(t, state.PutSubnetOnlyValidator(registerSubnetValidatorSoV)) + require.NoError(t, state.PutSubnetOnlyValidator(convertSubnetValidatorSoV)) + state.PutExpiry(expiryEntry) + + tests := []struct { + name string + validationID ids.ID + justification []byte + expectedErr *common.AppError + }{ + { + name: "failed to parse justification", + justification: []byte("invalid"), + expectedErr: &common.AppError{ + Code: ErrFailedToParseJustification, + Message: "failed to parse justification: proto: cannot parse invalid wire-format data", + }, + }, + { + name: "failed to parse subnetID", + justification: must[[]byte](t)(proto.Marshal( + &platformvm.SubnetValidatorRegistrationJustification{ + Preimage: &platformvm.SubnetValidatorRegistrationJustification_ConvertSubnetTxData{}, + }, + )), + expectedErr: &common.AppError{ + Code: ErrFailedToParseSubnetID, + Message: "failed to parse subnetID: invalid hash length: expected 32 bytes but got 0", + }, + }, + { + name: "mismatched convert subnet validationID", + validationID: registerSubnetValidatorNotToRegister.ValidationID(), + justification: must[[]byte](t)(proto.Marshal( + &platformvm.SubnetValidatorRegistrationJustification{ + Preimage: &platformvm.SubnetValidatorRegistrationJustification_ConvertSubnetTxData{ + ConvertSubnetTxData: &platformvm.SubnetIDIndex{ + SubnetId: subnetID[:], + Index: 0, + }, + }, + }, + )), + expectedErr: &common.AppError{ + Code: ErrMismatchedValidationID, + Message: `validationID "2SZuDErFdUGmrQNHuTaFobL6DewfJr4tEKrdcgPNVc7PXYejGD" != justificationID "8XSRE5pasJjRvghBXQyBzDPF91ywXm8AZWZ6jo4522tbVuynN"`, + }, + }, + { + name: "convert subnet validation exists", + validationID: convertSubnetValidatorValidationID, + justification: must[[]byte](t)(proto.Marshal( + &platformvm.SubnetValidatorRegistrationJustification{ + Preimage: &platformvm.SubnetValidatorRegistrationJustification_ConvertSubnetTxData{ + ConvertSubnetTxData: &platformvm.SubnetIDIndex{ + SubnetId: subnetID[:], + Index: 0, + }, + }, + }, + )), + expectedErr: &common.AppError{ + Code: ErrValidationExists, + Message: `validation "8XSRE5pasJjRvghBXQyBzDPF91ywXm8AZWZ6jo4522tbVuynN" exists`, + }, + }, + { + name: "valid convert subnet data", + validationID: subnetID.Append(1), + justification: must[[]byte](t)(proto.Marshal( + &platformvm.SubnetValidatorRegistrationJustification{ + Preimage: &platformvm.SubnetValidatorRegistrationJustification_ConvertSubnetTxData{ + ConvertSubnetTxData: &platformvm.SubnetIDIndex{ + SubnetId: subnetID[:], + Index: 1, + }, + }, + }, + )), + }, + { + name: "failed to parse register subnet validator", + justification: must[[]byte](t)(proto.Marshal( + &platformvm.SubnetValidatorRegistrationJustification{ + Preimage: &platformvm.SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage{}, + }, + )), + expectedErr: &common.AppError{ + Code: ErrFailedToParseRegisterSubnetValidator, + Message: "failed to parse RegisterSubnetValidator justification: couldn't unpack codec version", + }, + }, + { + name: "mismatched registration validationID", + validationID: registerSubnetValidatorValidationID, + justification: must[[]byte](t)(proto.Marshal( + &platformvm.SubnetValidatorRegistrationJustification{ + Preimage: &platformvm.SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage{ + RegisterSubnetValidatorMessage: registerSubnetValidatorNotToRegister.Bytes(), + }, + }, + )), + expectedErr: &common.AppError{ + Code: ErrMismatchedValidationID, + Message: `validationID "2UB8nmhSCDbhBBzXkpvjZYAu37nC7spNGQAbkVSeWVvbT8RNqS" != justificationID "2SZuDErFdUGmrQNHuTaFobL6DewfJr4tEKrdcgPNVc7PXYejGD"`, + }, + }, + { + name: "registration validation exists", + validationID: registerSubnetValidatorValidationID, + justification: must[[]byte](t)(proto.Marshal( + &platformvm.SubnetValidatorRegistrationJustification{ + Preimage: &platformvm.SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage{ + RegisterSubnetValidatorMessage: registerSubnetValidatorToRegister.Bytes(), + }, + }, + )), + expectedErr: &common.AppError{ + Code: ErrValidationExists, + Message: `validation "2UB8nmhSCDbhBBzXkpvjZYAu37nC7spNGQAbkVSeWVvbT8RNqS" exists`, + }, + }, + { + name: "valid expired registration", + validationID: registerSubnetValidatorExpired.ValidationID(), + justification: must[[]byte](t)(proto.Marshal( + &platformvm.SubnetValidatorRegistrationJustification{ + Preimage: &platformvm.SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage{ + RegisterSubnetValidatorMessage: registerSubnetValidatorExpired.Bytes(), + }, + }, + )), + }, + { + name: "validation could be registered", + validationID: registerSubnetValidatorNotToRegister.ValidationID(), + justification: must[[]byte](t)(proto.Marshal( + &platformvm.SubnetValidatorRegistrationJustification{ + Preimage: &platformvm.SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage{ + RegisterSubnetValidatorMessage: registerSubnetValidatorNotToRegister.Bytes(), + }, + }, + )), + expectedErr: &common.AppError{ + Code: ErrValidationCouldBeRegistered, + Message: `validation "2SZuDErFdUGmrQNHuTaFobL6DewfJr4tEKrdcgPNVc7PXYejGD" can be registered until 1607144401`, + }, + }, + { + name: "validation removed", + validationID: registerSubnetValidatorToMarkExpired.ValidationID(), + justification: must[[]byte](t)(proto.Marshal( + &platformvm.SubnetValidatorRegistrationJustification{ + Preimage: &platformvm.SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage{ + RegisterSubnetValidatorMessage: registerSubnetValidatorToMarkExpired.Bytes(), + }, + }, + )), + }, + { + name: "invalid justification type", + expectedErr: &common.AppError{ + Code: ErrInvalidJustificationType, + Message: "invalid justification type: ", + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := s.Verify( + context.Background(), + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + constants.UnitTestID, + constants.PlatformChainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + nil, + must[*message.SubnetValidatorRegistration](t)(message.NewSubnetValidatorRegistration( + test.validationID, + false, + )).Bytes(), + )).Bytes(), + )), + test.justification, + ) + if err != nil { + // Replace use non-breaking spaces (U+00a0) with regular spaces + // (U+0020). This is needed because the proto library + // intentionally makes the error message unstable. + err.Message = strings.ReplaceAll(err.Message, " ", " ") + } + require.Equal(t, test.expectedErr, err) + }) + } +} + func must[T any](t require.TestingT) func(T, error) T { return func(val T, err error) T { require.NoError(t, err) From 683b7163f7a97e2390eb7a0b20207b6eae5f5927 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 10 Nov 2024 09:50:05 -0500 Subject: [PATCH 373/400] Add last warp signing test --- vms/platformvm/network/warp_test.go | 97 +++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/vms/platformvm/network/warp_test.go b/vms/platformvm/network/warp_test.go index 948e57916bcb..96687509bf4d 100644 --- a/vms/platformvm/network/warp_test.go +++ b/vms/platformvm/network/warp_test.go @@ -6,6 +6,7 @@ package network import ( "context" "encoding/hex" + "math" "strings" "sync" "testing" @@ -521,6 +522,102 @@ func TestSignatureRequestVerifySubnetValidatorRegistrationNotRegistered(t *testi } } +func TestSignatureRequestVerifySubnetValidatorWeight(t *testing.T) { + sk, err := bls.NewSecretKey() + require.NoError(t, err) + + const ( + weight = 100 + nonce = 10 + ) + var ( + sov = state.SubnetOnlyValidator{ + ValidationID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + NodeID: ids.GenerateTestNodeID(), + PublicKey: bls.PublicKeyToUncompressedBytes(bls.PublicFromSecretKey(sk)), + Weight: weight, + MinNonce: nonce + 1, + } + + state = statetest.New(t, statetest.Config{}) + s = signatureRequestVerifier{ + stateLock: &sync.Mutex{}, + state: state, + } + ) + + require.NoError(t, state.PutSubnetOnlyValidator(sov)) + + tests := []struct { + name string + validationID ids.ID + nonce uint64 + weight uint64 + expectedErr *common.AppError + }{ + { + name: "impossible nonce", + nonce: math.MaxUint64, + expectedErr: &common.AppError{ + Code: ErrImpossibleNonce, + Message: "impossible nonce", + }, + }, + { + name: "validation does not exist", + expectedErr: &common.AppError{ + Code: ErrValidationDoesNotExist, + Message: `validation "11111111111111111111111111111111LpoYY" does not exist`, + }, + }, + { + name: "wrong nonce", + validationID: sov.ValidationID, + expectedErr: &common.AppError{ + Code: ErrWrongNonce, + Message: "provided nonce 0 != expected nonce (11 - 1)", + }, + }, + { + name: "wrong weight", + validationID: sov.ValidationID, + nonce: nonce, + expectedErr: &common.AppError{ + Code: ErrWrongWeight, + Message: "provided weight 0 != expected weight 100", + }, + }, + { + name: "valid", + validationID: sov.ValidationID, + nonce: nonce, + weight: weight, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := s.Verify( + context.Background(), + must[*warp.UnsignedMessage](t)(warp.NewUnsignedMessage( + constants.UnitTestID, + constants.PlatformChainID, + must[*payload.AddressedCall](t)(payload.NewAddressedCall( + nil, + must[*message.SubnetValidatorWeight](t)(message.NewSubnetValidatorWeight( + test.validationID, + test.nonce, + test.weight, + )).Bytes(), + )).Bytes(), + )), + nil, + ) + require.Equal(t, test.expectedErr, err) + }) + } +} + func must[T any](t require.TestingT) func(T, error) T { return func(val T, err error) T { require.NoError(t, err) From 70799d18300c3335c75635ff016d9d99df635cb2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 10 Nov 2024 10:23:25 -0500 Subject: [PATCH 374/400] Fix premature conversion validator exit signing --- vms/platformvm/network/warp.go | 27 ++++++++++++--- vms/platformvm/network/warp_test.go | 53 ++++++++++++++++++++--------- 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/vms/platformvm/network/warp.go b/vms/platformvm/network/warp.go index 911e407263f3..69cc41cc5bcd 100644 --- a/vms/platformvm/network/warp.go +++ b/vms/platformvm/network/warp.go @@ -212,7 +212,25 @@ func (s signatureRequestVerifier) verifySubnetValidatorNotCurrentlyRegistered( s.stateLock.Lock() defer s.stateLock.Unlock() - // Verify that the validator does not currently exist + // Verify that the provided validationID either: + // - Is in the current state + // - Was removed from the current state + // - Was not included in the subnet conversion + _, 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(), + } + } + + // Verify that the validator is not in the current state _, err = s.state.GetSubnetOnlyValidator(validationID) if err == nil { return &common.AppError{ @@ -226,6 +244,8 @@ func (s signatureRequestVerifier) verifySubnetValidatorNotCurrentlyRegistered( Message: "failed to lookup subnet only validator: " + err.Error(), } } + + // Either the validator was removed or it was never registered. return nil } @@ -254,7 +274,7 @@ func (s signatureRequestVerifier) verifySubnetValidatorCanNotValidate( s.stateLock.Lock() defer s.stateLock.Unlock() - // Verify that the validator does not and can never exists + // Verify that the validator does not currently exist _, err = s.state.GetSubnetOnlyValidator(validationID) if err == nil { return &common.AppError{ @@ -271,8 +291,7 @@ func (s signatureRequestVerifier) verifySubnetValidatorCanNotValidate( currentTimeUnix := uint64(s.state.GetTimestamp().Unix()) if justification.Expiry <= currentTimeUnix { - // The validator is not registered and the expiry time has passed - return nil + return nil // The expiry time has passed } hasExpiry, err := s.state.HasExpiry(state.ExpiryEntry{ diff --git a/vms/platformvm/network/warp_test.go b/vms/platformvm/network/warp_test.go index 96687509bf4d..59eba4947356 100644 --- a/vms/platformvm/network/warp_test.go +++ b/vms/platformvm/network/warp_test.go @@ -244,18 +244,19 @@ func TestSignatureRequestVerifySubnetValidatorRegistrationNotRegistered(t *testi require.NoError(t, err) var ( - subnetID = ids.ID{3} - nodeID0 = ids.NodeID{4} - nodeID1 = ids.NodeID{5} - nodeID2 = ids.NodeID{6} - nodeID3 = ids.NodeID{6} - pk = bls.PublicFromSecretKey(sk) - expiry = genesistest.DefaultValidatorStartTimeUnix + 1 - weight uint64 = 1 + convertedSubnetID = ids.ID{3} + unconvertedSubnetID = ids.ID{7} + nodeID0 = ids.NodeID{4} + nodeID1 = ids.NodeID{5} + nodeID2 = ids.NodeID{6} + nodeID3 = ids.NodeID{6} + pk = bls.PublicFromSecretKey(sk) + expiry = genesistest.DefaultValidatorStartTimeUnix + 1 + weight uint64 = 1 ) registerSubnetValidatorToRegister, err := message.NewRegisterSubnetValidator( - subnetID, + convertedSubnetID, nodeID0, [bls.PublicKeyLen]byte(bls.PublicKeyToCompressedBytes(pk)), expiry, @@ -266,7 +267,7 @@ func TestSignatureRequestVerifySubnetValidatorRegistrationNotRegistered(t *testi require.NoError(t, err) registerSubnetValidatorNotToRegister, err := message.NewRegisterSubnetValidator( - subnetID, + convertedSubnetID, nodeID1, [bls.PublicKeyLen]byte(bls.PublicKeyToCompressedBytes(pk)), expiry, @@ -277,7 +278,7 @@ func TestSignatureRequestVerifySubnetValidatorRegistrationNotRegistered(t *testi require.NoError(t, err) registerSubnetValidatorExpired, err := message.NewRegisterSubnetValidator( - subnetID, + convertedSubnetID, nodeID2, [bls.PublicKeyLen]byte(bls.PublicKeyToCompressedBytes(pk)), genesistest.DefaultValidatorStartTimeUnix, @@ -288,7 +289,7 @@ func TestSignatureRequestVerifySubnetValidatorRegistrationNotRegistered(t *testi require.NoError(t, err) registerSubnetValidatorToMarkExpired, err := message.NewRegisterSubnetValidator( - subnetID, + convertedSubnetID, nodeID3, [bls.PublicKeyLen]byte(bls.PublicKeyToCompressedBytes(pk)), expiry, @@ -299,6 +300,7 @@ func TestSignatureRequestVerifySubnetValidatorRegistrationNotRegistered(t *testi require.NoError(t, err) var ( + conversion = state.SubnetConversion{} registerSubnetValidatorValidationID = registerSubnetValidatorToRegister.ValidationID() registerSubnetValidatorSoV = state.SubnetOnlyValidator{ ValidationID: registerSubnetValidatorValidationID, @@ -327,6 +329,7 @@ func TestSignatureRequestVerifySubnetValidatorRegistrationNotRegistered(t *testi } ) + state.SetSubnetConversion(convertedSubnetID, conversion) require.NoError(t, state.PutSubnetOnlyValidator(registerSubnetValidatorSoV)) require.NoError(t, state.PutSubnetOnlyValidator(convertSubnetValidatorSoV)) state.PutExpiry(expiryEntry) @@ -364,7 +367,7 @@ func TestSignatureRequestVerifySubnetValidatorRegistrationNotRegistered(t *testi &platformvm.SubnetValidatorRegistrationJustification{ Preimage: &platformvm.SubnetValidatorRegistrationJustification_ConvertSubnetTxData{ ConvertSubnetTxData: &platformvm.SubnetIDIndex{ - SubnetId: subnetID[:], + SubnetId: convertedSubnetID[:], Index: 0, }, }, @@ -382,7 +385,7 @@ func TestSignatureRequestVerifySubnetValidatorRegistrationNotRegistered(t *testi &platformvm.SubnetValidatorRegistrationJustification{ Preimage: &platformvm.SubnetValidatorRegistrationJustification_ConvertSubnetTxData{ ConvertSubnetTxData: &platformvm.SubnetIDIndex{ - SubnetId: subnetID[:], + SubnetId: convertedSubnetID[:], Index: 0, }, }, @@ -393,14 +396,32 @@ func TestSignatureRequestVerifySubnetValidatorRegistrationNotRegistered(t *testi Message: `validation "8XSRE5pasJjRvghBXQyBzDPF91ywXm8AZWZ6jo4522tbVuynN" exists`, }, }, + { + name: "conversion does not exist", + validationID: unconvertedSubnetID.Append(0), + justification: must[[]byte](t)(proto.Marshal( + &platformvm.SubnetValidatorRegistrationJustification{ + Preimage: &platformvm.SubnetValidatorRegistrationJustification_ConvertSubnetTxData{ + ConvertSubnetTxData: &platformvm.SubnetIDIndex{ + SubnetId: unconvertedSubnetID[:], + Index: 0, + }, + }, + }, + )), + expectedErr: &common.AppError{ + Code: ErrConversionDoesNotExist, + Message: `subnet "45oj4CqFViNHUtBxJ55TZfqaVAXFwMRMj2XkHVqUYjJYoTaEM" has not been converted`, + }, + }, { name: "valid convert subnet data", - validationID: subnetID.Append(1), + validationID: convertedSubnetID.Append(1), justification: must[[]byte](t)(proto.Marshal( &platformvm.SubnetValidatorRegistrationJustification{ Preimage: &platformvm.SubnetValidatorRegistrationJustification_ConvertSubnetTxData{ ConvertSubnetTxData: &platformvm.SubnetIDIndex{ - SubnetId: subnetID[:], + SubnetId: convertedSubnetID[:], Index: 1, }, }, From 2396649903e17f9400f364f860c9422b3143dd17 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 10 Nov 2024 10:37:18 -0500 Subject: [PATCH 375/400] Update comments --- proto/pb/platformvm/platformvm.pb.go | 10 +++++++--- proto/platformvm/platformvm.proto | 10 +++++++--- vms/platformvm/network/warp.go | 13 ++++++++----- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/proto/pb/platformvm/platformvm.pb.go b/proto/pb/platformvm/platformvm.pb.go index 15c2e96f37f2..452e02d077d0 100644 --- a/proto/pb/platformvm/platformvm.pb.go +++ b/proto/pb/platformvm/platformvm.pb.go @@ -90,13 +90,17 @@ type isSubnetValidatorRegistrationJustification_Preimage interface { } type SubnetValidatorRegistrationJustification_ConvertSubnetTxData struct { - // Validator was added to the Subnet during the ConvertSubnetTx. + // This should be set to obtain an attestation that a validator specified in + // a ConvertSubnetTx has been removed from the validator set. 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 + // This should be set to a RegisterSubnetValidatorMessage to obtain an + // attestation that a validator is not currently registered and can never be + // registered. This can be because the validator was successfully added and + // then later removed, or because the validator was never added and the + // registration expired. RegisterSubnetValidatorMessage []byte `protobuf:"bytes,2,opt,name=register_subnet_validator_message,json=registerSubnetValidatorMessage,proto3,oneof"` } diff --git a/proto/platformvm/platformvm.proto b/proto/platformvm/platformvm.proto index 571be0d7862b..f1688d16592c 100644 --- a/proto/platformvm/platformvm.proto +++ b/proto/platformvm/platformvm.proto @@ -6,10 +6,14 @@ option go_package = "github.com/ava-labs/avalanchego/proto/pb/platformvm"; message SubnetValidatorRegistrationJustification { oneof preimage { - // Validator was added to the Subnet during the ConvertSubnetTx. + // This should be set to obtain an attestation that a validator specified in + // a ConvertSubnetTx has been removed from the validator set. SubnetIDIndex convert_subnet_tx_data = 1; - // Validator was registered to the Subnet after the ConvertSubnetTx. - // The SubnetValidator is being removed from the Subnet + // This should be set to a RegisterSubnetValidatorMessage to obtain an + // attestation that a validator is not currently registered and can never be + // registered. This can be because the validator was successfully added and + // then later removed, or because the validator was never added and the + // registration expired. bytes register_subnet_validator_message = 2; } } diff --git a/vms/platformvm/network/warp.go b/vms/platformvm/network/warp.go index 69cc41cc5bcd..339de22e073e 100644 --- a/vms/platformvm/network/warp.go +++ b/vms/platformvm/network/warp.go @@ -162,8 +162,8 @@ func (s signatureRequestVerifier) verifySubnetValidatorRegistration( } } -// verifySubnetValidatorCanNotValidate verifies that the validationID is -// currently a validator. +// verifySubnetValidatorRegistered verifies that the validationID is currently a +// validator. func (s signatureRequestVerifier) verifySubnetValidatorRegistered( validationID ids.ID, ) *common.AppError { @@ -187,8 +187,9 @@ func (s signatureRequestVerifier) verifySubnetValidatorRegistered( return nil } -// verifySubnetValidatorCanNotValidate verifies that the validationID is not -// currently a validator. +// verifySubnetValidatorNotCurrentlyRegistered verifies that the validationID +// could only correspond to a validator from a ConvertSubnetTx and that it is +// not currently a validator. func (s signatureRequestVerifier) verifySubnetValidatorNotCurrentlyRegistered( validationID ids.ID, justification *platformvm.SubnetIDIndex, @@ -249,7 +250,7 @@ func (s signatureRequestVerifier) verifySubnetValidatorNotCurrentlyRegistered( return nil } -// verifySubnetValidatorCanNotValidate verifies that the validationID does not +// verifySubnetValidatorCanNotValidate verifies that the validationID is not // currently and can never become a validator. func (s signatureRequestVerifier) verifySubnetValidatorCanNotValidate( validationID ids.ID, @@ -294,6 +295,8 @@ func (s signatureRequestVerifier) verifySubnetValidatorCanNotValidate( return nil // The expiry time has passed } + // If the validation ID was successfully registered and then removed, it can + // never be re-used again even if its expiry has not yet passed. hasExpiry, err := s.state.HasExpiry(state.ExpiryEntry{ Timestamp: justification.Expiry, ValidationID: validationID, From fa6227a0bc06bb0efce91d57dad760276cbbf28d Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 10 Nov 2024 11:50:25 -0500 Subject: [PATCH 376/400] Remove todo --- vms/platformvm/network/network_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/network/network_test.go b/vms/platformvm/network/network_test.go index d47ef1fcf614..b63c1aedba3e 100644 --- a/vms/platformvm/network/network_test.go +++ b/vms/platformvm/network/network_test.go @@ -176,7 +176,7 @@ func TestNetworkIssueTxFromRPC(t *testing.T) { tt.mempoolFunc(ctrl), tt.partialSyncPrimaryNetwork, tt.appSenderFunc(ctrl), - nil, // TODO: Populate and test + nil, nil, nil, prometheus.NewRegistry(), From a60c82b7648f3b10e2fda2042a82069862c9500a Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 10 Nov 2024 12:14:27 -0500 Subject: [PATCH 377/400] reduce diff --- 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 d62b4e459c25..8602c26ffb64 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 := primary.LocalAPIURI kc := secp256k1fx.NewKeychain(key) subnetID := ids.FromStringOrPanic("2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof") - chainID := ids.FromStringOrPanic("4R1dLAnG45P3rbdJB2dWuKdVRZF3dLMKgfJ8J6wKSQvYFVUhb") + chainID := ids.FromStringOrPanic("E8nTR9TtRwfkS7XFjTYUYHENQ91mkPMtDUwwCeu7rNgBBtkqu") addressHex := "" weight := units.Schmeckle From a4637e7c2b1ed9ace3d99d9fc420e6597b377bd7 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Sun, 10 Nov 2024 12:47:37 -0500 Subject: [PATCH 378/400] Refactor P-Chain configs --- node/node.go | 2 +- vms/platformvm/block/builder/helpers_test.go | 8 +- vms/platformvm/block/executor/block_test.go | 20 +-- vms/platformvm/block/executor/helpers_test.go | 6 +- .../block/executor/verifier_test.go | 24 +-- vms/platformvm/config/config.go | 142 ++++++------------ ...xecution_config_test.go => config_test.go} | 20 ++- vms/platformvm/config/execution_config.go | 63 -------- vms/platformvm/config/internal.go | 115 ++++++++++++++ .../{network/config.go => config/network.go} | 6 +- vms/platformvm/factory.go | 4 +- vms/platformvm/network/network.go | 3 +- vms/platformvm/network/network_test.go | 3 +- vms/platformvm/service.go | 2 +- vms/platformvm/service_test.go | 6 +- vms/platformvm/state/chain_time_helpers.go | 4 +- .../state/chain_time_helpers_test.go | 2 +- vms/platformvm/state/state.go | 2 +- vms/platformvm/state/state_test.go | 2 +- vms/platformvm/state/statetest/state.go | 6 +- vms/platformvm/txs/executor/backend.go | 2 +- vms/platformvm/txs/executor/helpers_test.go | 8 +- .../executor/staker_tx_verification_test.go | 30 ++-- .../txs/executor/standard_tx_executor_test.go | 40 ++--- .../txs/executor/state_changes_test.go | 6 +- vms/platformvm/txs/txstest/context.go | 2 +- vms/platformvm/txs/txstest/wallet.go | 2 +- vms/platformvm/validator_set_property_test.go | 6 +- vms/platformvm/validators/manager.go | 4 +- .../validators/manager_benchmark_test.go | 2 +- vms/platformvm/validators/manager_test.go | 2 +- vms/platformvm/vm.go | 16 +- vms/platformvm/vm_regression_test.go | 18 +-- vms/platformvm/vm_test.go | 18 +-- 34 files changed, 298 insertions(+), 298 deletions(-) rename vms/platformvm/config/{execution_config_test.go => config_test.go} (88%) delete mode 100644 vms/platformvm/config/execution_config.go create mode 100644 vms/platformvm/config/internal.go rename vms/platformvm/{network/config.go => config/network.go} (98%) diff --git a/node/node.go b/node/node.go index 8cdc8edfce17..5e7c819a4d40 100644 --- a/node/node.go +++ b/node/node.go @@ -1207,7 +1207,7 @@ func (n *Node) initVMs() error { // Register the VMs that Avalanche supports err := errors.Join( n.VMManager.RegisterFactory(context.TODO(), constants.PlatformVMID, &platformvm.Factory{ - Config: platformconfig.Config{ + Internal: platformconfig.Internal{ Chains: n.chainManager, Validators: vdrs, UptimeLockedCalculator: n.uptimeCalculator, diff --git a/vms/platformvm/block/builder/helpers_test.go b/vms/platformvm/block/builder/helpers_test.go index c779609baf54..2d20ce56dc1a 100644 --- a/vms/platformvm/block/builder/helpers_test.go +++ b/vms/platformvm/block/builder/helpers_test.go @@ -75,7 +75,7 @@ type environment struct { sender *enginetest.Sender isBootstrapped *utils.Atomic[bool] - config *config.Config + config *config.Internal clk *mockable.Clock baseDB *versiondb.Database ctx *snow.Context @@ -167,7 +167,7 @@ func newEnvironment(t *testing.T, f upgradetest.Fork) *environment { //nolint:un res.backend.Config.PartialSyncPrimaryNetwork, res.sender, registerer, - network.DefaultConfig, + config.DefaultNetwork, ) require.NoError(err) @@ -260,7 +260,7 @@ func addSubnet(t *testing.T, env *environment) { require.NoError(env.state.Commit()) } -func defaultConfig(f upgradetest.Fork) *config.Config { +func defaultConfig(f upgradetest.Fork) *config.Internal { upgrades := upgradetest.GetConfigWithUpgradeTime(f, time.Time{}) // This package neglects fork ordering upgradetest.SetTimesTo( @@ -269,7 +269,7 @@ func defaultConfig(f upgradetest.Fork) *config.Config { genesistest.DefaultValidatorEndTime, ) - return &config.Config{ + return &config.Internal{ Chains: chains.TestManager, UptimeLockedCalculator: uptime.NewLockedCalculator(), Validators: validators.NewManager(), diff --git a/vms/platformvm/block/executor/block_test.go b/vms/platformvm/block/executor/block_test.go index b7a101d96bda..a33f1335e9a2 100644 --- a/vms/platformvm/block/executor/block_test.go +++ b/vms/platformvm/block/executor/block_test.go @@ -46,7 +46,7 @@ func TestBlockOptions(t *testing.T) { ctx: snowtest.Context(t, snowtest.PChainID), }, txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UptimePercentage: 0, }, Uptimes: uptimes, @@ -73,7 +73,7 @@ func TestBlockOptions(t *testing.T) { ctx: snowtest.Context(t, snowtest.PChainID), }, txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UptimePercentage: 0, }, Uptimes: uptimes, @@ -109,7 +109,7 @@ func TestBlockOptions(t *testing.T) { ctx: snowtest.Context(t, snowtest.PChainID), }, txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UptimePercentage: 0, }, Uptimes: uptimes, @@ -147,7 +147,7 @@ func TestBlockOptions(t *testing.T) { ctx: snowtest.Context(t, snowtest.PChainID), }, txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UptimePercentage: 0, }, Uptimes: uptimes, @@ -188,7 +188,7 @@ func TestBlockOptions(t *testing.T) { ctx: snowtest.Context(t, snowtest.PChainID), }, txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UptimePercentage: 0, }, Uptimes: uptimes, @@ -239,7 +239,7 @@ func TestBlockOptions(t *testing.T) { ctx: snowtest.Context(t, snowtest.PChainID), }, txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UptimePercentage: 0, }, Uptimes: uptimes, @@ -295,7 +295,7 @@ func TestBlockOptions(t *testing.T) { ctx: snowtest.Context(t, snowtest.PChainID), }, txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UptimePercentage: 0, }, Uptimes: uptimes, @@ -351,7 +351,7 @@ func TestBlockOptions(t *testing.T) { ctx: snowtest.Context(t, snowtest.PChainID), }, txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UptimePercentage: 0, }, Uptimes: uptimes, @@ -413,7 +413,7 @@ func TestBlockOptions(t *testing.T) { ctx: snowtest.Context(t, snowtest.PChainID), }, txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UptimePercentage: .8, }, Uptimes: uptimes, @@ -475,7 +475,7 @@ func TestBlockOptions(t *testing.T) { ctx: snowtest.Context(t, snowtest.PChainID), }, txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UptimePercentage: .8, }, Uptimes: uptimes, diff --git a/vms/platformvm/block/executor/helpers_test.go b/vms/platformvm/block/executor/helpers_test.go index d0616a8e374c..14554e248b5d 100644 --- a/vms/platformvm/block/executor/helpers_test.go +++ b/vms/platformvm/block/executor/helpers_test.go @@ -86,7 +86,7 @@ type environment struct { sender *enginetest.Sender isBootstrapped *utils.Atomic[bool] - config *config.Config + config *config.Internal clk *mockable.Clock baseDB *versiondb.Database ctx *snow.Context @@ -266,7 +266,7 @@ func addSubnet(t testing.TB, env *environment) { require.NoError(env.state.Commit()) } -func defaultConfig(f upgradetest.Fork) *config.Config { +func defaultConfig(f upgradetest.Fork) *config.Internal { upgrades := upgradetest.GetConfigWithUpgradeTime(f, time.Time{}) // This package neglects fork ordering upgradetest.SetTimesTo( @@ -275,7 +275,7 @@ func defaultConfig(f upgradetest.Fork) *config.Config { genesistest.DefaultValidatorEndTime, ) - return &config.Config{ + return &config.Internal{ Chains: chains.TestManager, UptimeLockedCalculator: uptime.NewLockedCalculator(), Validators: validators.NewManager(), diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 4de3a6c8b2dd..16b129b36882 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -96,7 +96,7 @@ func newTestVerifier(t testing.TB, c testVerifierConfig) *verifier { ctx: c.Context, }, txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ CreateAssetTxFee: genesis.LocalParams.CreateAssetTxFee, StaticFeeConfig: genesis.LocalParams.StaticFeeConfig, DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, @@ -460,7 +460,7 @@ func TestVerifierVisitCommitBlock(t *testing.T) { } manager := &manager{ txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), }, Clk: &mockable.Clock{}, @@ -527,7 +527,7 @@ func TestVerifierVisitAbortBlock(t *testing.T) { } manager := &manager{ txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), }, Clk: &mockable.Clock{}, @@ -582,7 +582,7 @@ func TestVerifyUnverifiedParent(t *testing.T) { } verifier := &verifier{ txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), }, Clk: &mockable.Clock{}, @@ -654,7 +654,7 @@ func TestBanffAbortBlockTimestampChecks(t *testing.T) { } verifier := &verifier{ txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.Banff), }, Clk: &mockable.Clock{}, @@ -754,7 +754,7 @@ func TestBanffCommitBlockTimestampChecks(t *testing.T) { } verifier := &verifier{ txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.Banff), }, Clk: &mockable.Clock{}, @@ -831,7 +831,7 @@ func TestVerifierVisitApricotStandardBlockWithProposalBlockParent(t *testing.T) } verifier := &verifier{ txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), }, Clk: &mockable.Clock{}, @@ -888,7 +888,7 @@ func TestVerifierVisitBanffStandardBlockWithProposalBlockParent(t *testing.T) { } verifier := &verifier{ txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.Banff), }, Clk: &mockable.Clock{}, @@ -925,7 +925,7 @@ func TestVerifierVisitApricotCommitBlockUnexpectedParentState(t *testing.T) { parentStatelessBlk := block.NewMockBlock(ctrl) verifier := &verifier{ txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), }, Clk: &mockable.Clock{}, @@ -968,7 +968,7 @@ func TestVerifierVisitBanffCommitBlockUnexpectedParentState(t *testing.T) { timestamp := time.Unix(12345, 0) verifier := &verifier{ txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.Banff), }, Clk: &mockable.Clock{}, @@ -1012,7 +1012,7 @@ func TestVerifierVisitApricotAbortBlockUnexpectedParentState(t *testing.T) { parentStatelessBlk := block.NewMockBlock(ctrl) verifier := &verifier{ txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), }, Clk: &mockable.Clock{}, @@ -1055,7 +1055,7 @@ func TestVerifierVisitBanffAbortBlockUnexpectedParentState(t *testing.T) { timestamp := time.Unix(12345, 0) verifier := &verifier{ txExecutorBackend: &executor.Backend{ - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.Banff), }, Clk: &mockable.Clock{}, diff --git a/vms/platformvm/config/config.go b/vms/platformvm/config/config.go index 897f910a1380..f66d3c69fa32 100644 --- a/vms/platformvm/config/config.go +++ b/vms/platformvm/config/config.go @@ -4,111 +4,59 @@ package config import ( + "encoding/json" "time" - "github.com/ava-labs/avalanchego/chains" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/uptime" - "github.com/ava-labs/avalanchego/snow/validators" - "github.com/ava-labs/avalanchego/upgrade" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/set" - "github.com/ava-labs/avalanchego/vms/components/gas" - "github.com/ava-labs/avalanchego/vms/platformvm/reward" - "github.com/ava-labs/avalanchego/vms/platformvm/txs" - - txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" - validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" + "github.com/ava-labs/avalanchego/utils/units" ) -// Struct collecting all foundational parameters of PlatformVM -type Config struct { - // The node's chain manager - Chains chains.Manager - - // Node's validator set maps subnetID -> validators of the subnet - // - // Invariant: The primary network's validator set should have been added to - // the manager before calling VM.Initialize. - // Invariant: The primary network's validator set should be empty before - // calling VM.Initialize. - Validators validators.Manager - - // Static fees are active before Etna - CreateAssetTxFee uint64 // Override for CreateSubnet and CreateChain before AP3 - StaticFeeConfig txfee.StaticConfig - - // Dynamic fees are active after Etna - DynamicFeeConfig gas.Config - - // ACP-77 validator fees are active after Etna - ValidatorFeeConfig validatorfee.Config - - // Provides access to the uptime manager as a thread safe data structure - UptimeLockedCalculator uptime.LockedCalculator - - // True if the node is being run with staking enabled - SybilProtectionEnabled bool - - // If true, only the P-chain will be instantiated on the primary network. - PartialSyncPrimaryNetwork bool - - // Set of subnets that this node is validating - TrackedSubnets set.Set[ids.ID] - - // The minimum amount of tokens one must bond to be a validator - MinValidatorStake uint64 - - // The maximum amount of tokens that can be bonded on a validator - MaxValidatorStake uint64 - - // Minimum stake, in nAVAX, that can be delegated on the primary network - MinDelegatorStake uint64 - - // Minimum fee that can be charged for delegation - MinDelegationFee uint32 - - // UptimePercentage is the minimum uptime required to be rewarded for staking - UptimePercentage float64 - - // Minimum amount of time to allow a staker to stake - MinStakeDuration time.Duration - - // Maximum amount of time to allow a staker to stake - MaxStakeDuration time.Duration - - // Config for the minting function - RewardConfig reward.Config - - // All network upgrade timestamps - UpgradeConfig upgrade.Config +var Default = Config{ + Network: DefaultNetwork, + BlockCacheSize: 64 * units.MiB, + TxCacheSize: 128 * units.MiB, + TransformedSubnetTxCacheSize: 4 * units.MiB, + RewardUTXOsCacheSize: 2048, + ChainCacheSize: 2048, + ChainDBCacheSize: 2048, + BlockIDCacheSize: 8192, + FxOwnerCacheSize: 4 * units.MiB, + SubnetConversionCacheSize: 4 * units.MiB, + L1WeightsCacheSize: 16 * units.KiB, + L1InactiveValidatorsCacheSize: 256 * units.KiB, + L1SubnetIDNodeIDCacheSize: 16 * units.KiB, + ChecksumsEnabled: false, + MempoolPruneFrequency: 30 * time.Minute, +} - // UseCurrentHeight forces [GetMinimumHeight] to return the current height - // of the P-Chain instead of the oldest block in the [recentlyAccepted] - // window. - // - // This config is particularly useful for triggering proposervm activation - // on recently created subnets (without this, users need to wait for - // [recentlyAcceptedWindowTTL] to pass for activation to occur). - UseCurrentHeight bool +// Config contains all of the user-configurable parameters of the PlatformVM. +type Config struct { + Network Network `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"` + SubnetConversionCacheSize int `json:"subnet-conversion-cache-size"` + L1WeightsCacheSize int `json:"l1-weights-cache-size"` + L1InactiveValidatorsCacheSize int `json:"l1-inactive-validators-cache-size"` + L1SubnetIDNodeIDCacheSize int `json:"l1-subnet-id-node-id-cache-size"` + ChecksumsEnabled bool `json:"checksums-enabled"` + MempoolPruneFrequency time.Duration `json:"mempool-prune-frequency"` } -// Create the blockchain described in [tx], but only if this node is a member of -// the subnet that validates the chain -func (c *Config) CreateChain(chainID ids.ID, tx *txs.CreateChainTx) { - if c.SybilProtectionEnabled && // Sybil protection is enabled, so nodes might not validate all chains - constants.PrimaryNetworkID != tx.SubnetID && // All nodes must validate the primary network - !c.TrackedSubnets.Contains(tx.SubnetID) { // This node doesn't validate this blockchain - return - } +// GetConfig returns an ExecutionConfig +// input is unmarshalled into an ExecutionConfig previously +// initialized with default values +func GetConfig(b []byte) (*Config, error) { + ec := Default - chainParams := chains.ChainParameters{ - ID: chainID, - SubnetID: tx.SubnetID, - GenesisData: tx.GenesisData, - VMID: tx.VMID, - FxIDs: tx.FxIDs, + // if bytes are empty keep default values + if len(b) == 0 { + return &ec, nil } - c.Chains.QueueChainCreation(chainParams) + return &ec, json.Unmarshal(b, &ec) } diff --git a/vms/platformvm/config/execution_config_test.go b/vms/platformvm/config/config_test.go similarity index 88% rename from vms/platformvm/config/execution_config_test.go rename to vms/platformvm/config/config_test.go index f4b077689b23..a9ad976bb559 100644 --- a/vms/platformvm/config/execution_config_test.go +++ b/vms/platformvm/config/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 @@ -35,25 +33,25 @@ func TestExecutionConfigUnmarshal(t *testing.T) { t.Run("default values from empty json", func(t *testing.T) { require := require.New(t) b := []byte(`{}`) - ec, err := GetExecutionConfig(b) + ec, err := GetConfig(b) require.NoError(err) - require.Equal(&DefaultExecutionConfig, ec) + require.Equal(&Default, ec) }) t.Run("default values from empty bytes", func(t *testing.T) { require := require.New(t) b := []byte(``) - ec, err := GetExecutionConfig(b) + ec, err := GetConfig(b) require.NoError(err) - require.Equal(&DefaultExecutionConfig, ec) + require.Equal(&Default, ec) }) t.Run("mix default and extracted values from json", func(t *testing.T) { require := require.New(t) b := []byte(`{"block-cache-size":1}`) - ec, err := GetExecutionConfig(b) + ec, err := GetConfig(b) require.NoError(err) - expected := DefaultExecutionConfig + expected := Default expected.BlockCacheSize = 1 require.Equal(&expected, ec) }) @@ -61,8 +59,8 @@ func TestExecutionConfigUnmarshal(t *testing.T) { t.Run("all values extracted from json", func(t *testing.T) { require := require.New(t) - expected := &ExecutionConfig{ - Network: network.Config{ + expected := &Config{ + Network: Network{ MaxValidatorSetStaleness: 1, TargetGossipSize: 2, PushGossipPercentStake: .3, @@ -102,7 +100,7 @@ func TestExecutionConfigUnmarshal(t *testing.T) { b, err := json.Marshal(expected) require.NoError(err) - actual, err := GetExecutionConfig(b) + actual, err := GetConfig(b) require.NoError(err) require.Equal(expected, actual) }) diff --git a/vms/platformvm/config/execution_config.go b/vms/platformvm/config/execution_config.go deleted file mode 100644 index 2c63c6299e58..000000000000 --- a/vms/platformvm/config/execution_config.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package config - -import ( - "encoding/json" - "time" - - "github.com/ava-labs/avalanchego/utils/units" - "github.com/ava-labs/avalanchego/vms/platformvm/network" -) - -var DefaultExecutionConfig = ExecutionConfig{ - Network: network.DefaultConfig, - BlockCacheSize: 64 * units.MiB, - TxCacheSize: 128 * units.MiB, - TransformedSubnetTxCacheSize: 4 * units.MiB, - RewardUTXOsCacheSize: 2048, - ChainCacheSize: 2048, - ChainDBCacheSize: 2048, - BlockIDCacheSize: 8192, - FxOwnerCacheSize: 4 * units.MiB, - SubnetConversionCacheSize: 4 * units.MiB, - L1WeightsCacheSize: 16 * units.KiB, - L1InactiveValidatorsCacheSize: 256 * units.KiB, - L1SubnetIDNodeIDCacheSize: 16 * units.KiB, - ChecksumsEnabled: false, - MempoolPruneFrequency: 30 * time.Minute, -} - -// 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"` - SubnetConversionCacheSize int `json:"subnet-conversion-cache-size"` - L1WeightsCacheSize int `json:"l1-weights-cache-size"` - L1InactiveValidatorsCacheSize int `json:"l1-inactive-validators-cache-size"` - L1SubnetIDNodeIDCacheSize int `json:"l1-subnet-id-node-id-cache-size"` - ChecksumsEnabled bool `json:"checksums-enabled"` - MempoolPruneFrequency time.Duration `json:"mempool-prune-frequency"` -} - -// GetExecutionConfig returns an ExecutionConfig -// input is unmarshalled into an ExecutionConfig previously -// initialized with default values -func GetExecutionConfig(b []byte) (*ExecutionConfig, error) { - ec := DefaultExecutionConfig - - // if bytes are empty keep default values - if len(b) == 0 { - return &ec, nil - } - - return &ec, json.Unmarshal(b, &ec) -} diff --git a/vms/platformvm/config/internal.go b/vms/platformvm/config/internal.go new file mode 100644 index 000000000000..32e59a319aca --- /dev/null +++ b/vms/platformvm/config/internal.go @@ -0,0 +1,115 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package config + +import ( + "time" + + "github.com/ava-labs/avalanchego/chains" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/uptime" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/upgrade" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/vms/components/gas" + "github.com/ava-labs/avalanchego/vms/platformvm/reward" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + + txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" +) + +// Internal contains all of the parameters for the PlatformVM that are +// internally set by the node. +type Internal struct { + // The node's chain manager + Chains chains.Manager + + // Node's validator set maps subnetID -> validators of the subnet + // + // Invariant: The primary network's validator set should have been added to + // the manager before calling VM.Initialize. + // Invariant: The primary network's validator set should be empty before + // calling VM.Initialize. + Validators validators.Manager + + // Static fees are active before Etna + CreateAssetTxFee uint64 // Override for CreateSubnet and CreateChain before AP3 + StaticFeeConfig txfee.StaticConfig + + // Dynamic fees are active after Etna + DynamicFeeConfig gas.Config + + // ACP-77 validator fees are active after Etna + ValidatorFeeConfig validatorfee.Config + + // Provides access to the uptime manager as a thread safe data structure + UptimeLockedCalculator uptime.LockedCalculator + + // True if the node is being run with staking enabled + SybilProtectionEnabled bool + + // If true, only the P-chain will be instantiated on the primary network. + PartialSyncPrimaryNetwork bool + + // Set of subnets that this node is validating + TrackedSubnets set.Set[ids.ID] + + // The minimum amount of tokens one must bond to be a validator + MinValidatorStake uint64 + + // The maximum amount of tokens that can be bonded on a validator + MaxValidatorStake uint64 + + // Minimum stake, in nAVAX, that can be delegated on the primary network + MinDelegatorStake uint64 + + // Minimum fee that can be charged for delegation + MinDelegationFee uint32 + + // UptimePercentage is the minimum uptime required to be rewarded for staking + UptimePercentage float64 + + // Minimum amount of time to allow a staker to stake + MinStakeDuration time.Duration + + // Maximum amount of time to allow a staker to stake + MaxStakeDuration time.Duration + + // Config for the minting function + RewardConfig reward.Config + + // All network upgrade timestamps + UpgradeConfig upgrade.Config + + // UseCurrentHeight forces [GetMinimumHeight] to return the current height + // of the P-Chain instead of the oldest block in the [recentlyAccepted] + // window. + // + // This config is particularly useful for triggering proposervm activation + // on recently created subnets (without this, users need to wait for + // [recentlyAcceptedWindowTTL] to pass for activation to occur). + UseCurrentHeight bool +} + +// Create the blockchain described in [tx], but only if this node is a member of +// the subnet that validates the chain +func (c *Internal) CreateChain(chainID ids.ID, tx *txs.CreateChainTx) { + if c.SybilProtectionEnabled && // Sybil protection is enabled, so nodes might not validate all chains + constants.PrimaryNetworkID != tx.SubnetID && // All nodes must validate the primary network + !c.TrackedSubnets.Contains(tx.SubnetID) { // This node doesn't validate this blockchain + return + } + + chainParams := chains.ChainParameters{ + ID: chainID, + SubnetID: tx.SubnetID, + GenesisData: tx.GenesisData, + VMID: tx.VMID, + FxIDs: tx.FxIDs, + } + + c.Chains.QueueChainCreation(chainParams) +} diff --git a/vms/platformvm/network/config.go b/vms/platformvm/config/network.go similarity index 98% rename from vms/platformvm/network/config.go rename to vms/platformvm/config/network.go index 797138ab93f9..a96394a1aad6 100644 --- a/vms/platformvm/network/config.go +++ b/vms/platformvm/config/network.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 DefaultNetwork = Network{ MaxValidatorSetStaleness: time.Minute, TargetGossipSize: 20 * units.KiB, PushGossipPercentStake: .9, @@ -29,7 +29,7 @@ var DefaultConfig = Config{ MaxBloomFilterFalsePositiveProbability: .05, } -type Config struct { +type Network 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/factory.go b/vms/platformvm/factory.go index 834c9c8f2450..12cd5cbce69f 100644 --- a/vms/platformvm/factory.go +++ b/vms/platformvm/factory.go @@ -13,10 +13,10 @@ var _ vms.Factory = (*Factory)(nil) // Factory can create new instances of the Platform Chain type Factory struct { - config.Config + config.Internal } // New returns a new instance of the Platform Chain func (f *Factory) New(logging.Logger) (interface{}, error) { - return &VM{Config: f.Config}, nil + return &VM{Internal: f.Internal}, nil } diff --git a/vms/platformvm/network/network.go b/vms/platformvm/network/network.go index c821c8272511..aa95cd67888c 100644 --- a/vms/platformvm/network/network.go +++ b/vms/platformvm/network/network.go @@ -17,6 +17,7 @@ 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/txs" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" ) @@ -48,7 +49,7 @@ func New( partialSyncPrimaryNetwork bool, appSender common.AppSender, registerer prometheus.Registerer, - config Config, + config config.Network, ) (*Network, error) { p2pNetwork, err := p2p.NewNetwork(log, appSender, registerer, "p2p") if err != nil { diff --git a/vms/platformvm/network/network_test.go b/vms/platformvm/network/network_test.go index f649c677450c..2dc6e2852585 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.Network{ MaxValidatorSetStaleness: time.Second, TargetGossipSize: 1, PushGossipNumValidators: 1, diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 22153a97ece6..b7e9fad6e125 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -1863,7 +1863,7 @@ func (s *Service) GetFeeConfig(_ *http.Request, _ *struct{}, reply *gas.Config) // TODO: Remove after Etna is activated. now := time.Now() - if !s.vm.Config.UpgradeConfig.IsEtnaActivated(now) { + if !s.vm.Internal.UpgradeConfig.IsEtnaActivated(now) { return nil } diff --git a/vms/platformvm/service_test.go b/vms/platformvm/service_test.go index 0f09bc3a25fa..2eeeedf20216 100644 --- a/vms/platformvm/service_test.go +++ b/vms/platformvm/service_test.go @@ -373,7 +373,7 @@ func TestGetBalance(t *testing.T) { require := require.New(t) service, _ := defaultService(t, upgradetest.Durango) - feeCalculator := state.PickFeeCalculator(&service.vm.Config, service.vm.state) + feeCalculator := state.PickFeeCalculator(&service.vm.Internal, service.vm.state) createSubnetFee, err := feeCalculator.CalculateFee(testSubnet1.Unsigned) require.NoError(err) @@ -795,7 +795,7 @@ func TestGetBlock(t *testing.T) { service, _ := defaultService(t, upgradetest.Latest) service.vm.ctx.Lock.Lock() - service.vm.Config.CreateAssetTxFee = 100 * defaultTxFee + service.vm.Internal.CreateAssetTxFee = 100 * defaultTxFee subnetID := testSubnet1.ID() wallet := newWallet(t, service.vm, walletConfig{ @@ -1155,7 +1155,7 @@ func TestGetFeeConfig(t *testing.T) { require := require.New(t) service, _ := defaultService(t, upgradetest.Latest) - service.vm.Config.UpgradeConfig.EtnaTime = test.etnaTime + service.vm.Internal.UpgradeConfig.EtnaTime = test.etnaTime var reply gas.Config require.NoError(service.GetFeeConfig(nil, nil, &reply)) diff --git a/vms/platformvm/state/chain_time_helpers.go b/vms/platformvm/state/chain_time_helpers.go index fbf33b83e8d3..b6d4753df984 100644 --- a/vms/platformvm/state/chain_time_helpers.go +++ b/vms/platformvm/state/chain_time_helpers.go @@ -137,7 +137,7 @@ func getNextSoVEvictionTime( // depending on the active upgrade. // // PickFeeCalculator does not modify [state]. -func PickFeeCalculator(config *config.Config, state Chain) txfee.Calculator { +func PickFeeCalculator(config *config.Internal, state Chain) txfee.Calculator { timestamp := state.GetTimestamp() if !config.UpgradeConfig.IsEtnaActivated(timestamp) { return NewStaticFeeCalculator(config, timestamp) @@ -157,7 +157,7 @@ func PickFeeCalculator(config *config.Config, state Chain) txfee.Calculator { // NewStaticFeeCalculator creates a static fee calculator, with the config set // to either the pre-AP3 or post-AP3 config. -func NewStaticFeeCalculator(config *config.Config, timestamp time.Time) txfee.Calculator { +func NewStaticFeeCalculator(config *config.Internal, timestamp time.Time) txfee.Calculator { feeConfig := config.StaticFeeConfig if !config.UpgradeConfig.IsApricotPhase3Activated(timestamp) { feeConfig.CreateSubnetTxFee = config.CreateAssetTxFee diff --git a/vms/platformvm/state/chain_time_helpers_test.go b/vms/platformvm/state/chain_time_helpers_test.go index 66ba236e498c..548b3102cbc8 100644 --- a/vms/platformvm/state/chain_time_helpers_test.go +++ b/vms/platformvm/state/chain_time_helpers_test.go @@ -243,7 +243,7 @@ func TestPickFeeCalculator(t *testing.T) { for _, test := range tests { t.Run(test.fork.String(), func(t *testing.T) { var ( - config = &config.Config{ + config = &config.Internal{ CreateAssetTxFee: createAssetTxFee, StaticFeeConfig: staticFeeConfig, DynamicFeeConfig: dynamicFeeConfig, diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 12eebd953132..d2dfd34e47a8 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -526,7 +526,7 @@ func New( metricsReg prometheus.Registerer, validators validators.Manager, upgrades upgrade.Config, - execCfg *config.ExecutionConfig, + execCfg *config.Config, ctx *snow.Context, metrics metrics.Metrics, rewards reward.Calculator, diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 342365a6db46..2905e54e2089 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -61,7 +61,7 @@ func newTestState(t testing.TB, db database.Database) *state { prometheus.NewRegistry(), validators.NewManager(), upgradetest.GetConfig(upgradetest.Latest), - &config.DefaultExecutionConfig, + &config.Default, &snow.Context{ NetworkID: constants.UnitTestID, NodeID: ids.GenerateTestNodeID(), diff --git a/vms/platformvm/state/statetest/state.go b/vms/platformvm/state/statetest/state.go index 66998ea3cf10..0aa6c631b0d2 100644 --- a/vms/platformvm/state/statetest/state.go +++ b/vms/platformvm/state/statetest/state.go @@ -35,7 +35,7 @@ type Config struct { Registerer prometheus.Registerer Validators validators.Manager Upgrades upgrade.Config - ExecutionConfig config.ExecutionConfig + ExecutionConfig config.Config Context *snow.Context Metrics metrics.Metrics Rewards reward.Calculator @@ -57,8 +57,8 @@ func New(t testing.TB, c Config) state.State { if c.Upgrades == (upgrade.Config{}) { c.Upgrades = upgradetest.GetConfig(upgradetest.Latest) } - if c.ExecutionConfig == (config.ExecutionConfig{}) { - c.ExecutionConfig = config.DefaultExecutionConfig + if c.ExecutionConfig == (config.Config{}) { + c.ExecutionConfig = config.Default } if c.Context == nil { c.Context = &snow.Context{ diff --git a/vms/platformvm/txs/executor/backend.go b/vms/platformvm/txs/executor/backend.go index 847aefc16499..3c86062da379 100644 --- a/vms/platformvm/txs/executor/backend.go +++ b/vms/platformvm/txs/executor/backend.go @@ -15,7 +15,7 @@ import ( ) type Backend struct { - Config *config.Config + Config *config.Internal Ctx *snow.Context Clk *mockable.Clock Fx fx.Fx diff --git a/vms/platformvm/txs/executor/helpers_test.go b/vms/platformvm/txs/executor/helpers_test.go index bf6a6f7214c1..3823355795d7 100644 --- a/vms/platformvm/txs/executor/helpers_test.go +++ b/vms/platformvm/txs/executor/helpers_test.go @@ -64,7 +64,7 @@ type mutableSharedMemory struct { type environment struct { isBootstrapped *utils.Atomic[bool] - config *config.Config + config *config.Internal clk *mockable.Clock baseDB *versiondb.Database ctx *snow.Context @@ -172,7 +172,7 @@ func newEnvironment(t *testing.T, f upgradetest.Fork) *environment { } type walletConfig struct { - config *config.Config + config *config.Internal keys []*secp256k1.PrivateKey subnetIDs []ids.ID chainIDs []ids.ID @@ -233,7 +233,7 @@ func addSubnet(t *testing.T, env *environment) { require.NoError(env.state.Commit()) } -func defaultConfig(f upgradetest.Fork) *config.Config { +func defaultConfig(f upgradetest.Fork) *config.Internal { upgrades := upgradetest.GetConfigWithUpgradeTime( f, genesistest.DefaultValidatorStartTime.Add(-2*time.Second), @@ -244,7 +244,7 @@ func defaultConfig(f upgradetest.Fork) *config.Config { genesistest.DefaultValidatorEndTime, ) - return &config.Config{ + return &config.Internal{ Chains: chains.TestManager, UptimeLockedCalculator: uptime.NewLockedCalculator(), Validators: validators.NewManager(), diff --git a/vms/platformvm/txs/executor/staker_tx_verification_test.go b/vms/platformvm/txs/executor/staker_tx_verification_test.go index 037d789a8eb2..4b6e7a410bf7 100644 --- a/vms/platformvm/txs/executor/staker_tx_verification_test.go +++ b/vms/platformvm/txs/executor/staker_tx_verification_test.go @@ -112,7 +112,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { backendF: func(*gomock.Controller) *Backend { return &Backend{ Ctx: ctx, - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), }, } @@ -136,7 +136,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { backendF: func(*gomock.Controller) *Backend { return &Backend{ Ctx: ctx, - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), }, Bootstrapped: &utils.Atomic[bool]{}, @@ -162,7 +162,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped.Set(true) return &Backend{ Ctx: ctx, - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Cortina, activeForkTime), }, Bootstrapped: bootstrapped, @@ -188,7 +188,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped.Set(true) return &Backend{ Ctx: ctx, - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), }, Bootstrapped: bootstrapped, @@ -217,7 +217,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped.Set(true) return &Backend{ Ctx: ctx, - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), }, Bootstrapped: bootstrapped, @@ -246,7 +246,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped.Set(true) return &Backend{ Ctx: ctx, - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), }, Bootstrapped: bootstrapped, @@ -276,7 +276,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped.Set(true) return &Backend{ Ctx: ctx, - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), }, Bootstrapped: bootstrapped, @@ -309,7 +309,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped.Set(true) return &Backend{ Ctx: ctx, - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), }, Bootstrapped: bootstrapped, @@ -342,7 +342,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped.Set(true) return &Backend{ Ctx: ctx, - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), }, Bootstrapped: bootstrapped, @@ -377,7 +377,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped.Set(true) return &Backend{ Ctx: ctx, - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), }, Bootstrapped: bootstrapped, @@ -406,7 +406,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped.Set(true) return &Backend{ Ctx: ctx, - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), }, Bootstrapped: bootstrapped, @@ -451,7 +451,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { return &Backend{ FlowChecker: flowChecker, - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), StaticFeeConfig: fee.StaticConfig{ AddSubnetValidatorFee: 1, @@ -499,7 +499,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { return &Backend{ FlowChecker: flowChecker, - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), StaticFeeConfig: fee.StaticConfig{ AddSubnetValidatorFee: 1, @@ -560,7 +560,7 @@ func TestGetValidatorRules(t *testing.T) { } var ( - config = &config.Config{ + config = &config.Internal{ MinValidatorStake: 1, MaxValidatorStake: 2, MinStakeDuration: time.Second, @@ -679,7 +679,7 @@ func TestGetDelegatorRules(t *testing.T) { expectedErr error } var ( - config = &config.Config{ + config = &config.Internal{ MinDelegatorStake: 1, MaxValidatorStake: 2, MinStakeDuration: time.Second, diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index c392e2dfe030..394ff2b54f8c 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -1762,7 +1762,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { nil, ).AnyTimes() - cfg := &config.Config{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Etna, env.latestForkTime), } feeCalculator := state.NewStaticFeeCalculator(cfg, env.state.GetTimestamp()) @@ -1791,7 +1791,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { env.state = state.NewMockDiff(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() - cfg := &config.Config{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) @@ -1820,7 +1820,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(nil, database.ErrNotFound) env.state.EXPECT().GetPendingValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(nil, database.ErrNotFound) - cfg := &config.Config{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) @@ -1852,7 +1852,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(&staker, nil).Times(1) - cfg := &config.Config{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) @@ -1882,7 +1882,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil) - cfg := &config.Config{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) @@ -1911,7 +1911,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { 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{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) @@ -1942,7 +1942,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil) env.fx.EXPECT().VerifyPermission(gomock.Any(), env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(errTest) - cfg := &config.Config{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) @@ -1976,7 +1976,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), ).Return(errTest) - cfg := &config.Config{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) @@ -2135,7 +2135,7 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { env.state = state.NewMockDiff(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() - cfg := &config.Config{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) @@ -2163,7 +2163,7 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { env.state = state.NewMockDiff(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() - cfg := &config.Config{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), } feeCalculator := state.PickFeeCalculator(cfg, env.state) @@ -2192,7 +2192,7 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { env.state = state.NewMockDiff(ctrl) env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() - cfg := &config.Config{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), MaxStakeDuration: math.MaxInt64, } @@ -2232,7 +2232,7 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), ).Return(ErrFlowCheckFailed) - cfg := &config.Config{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), MaxStakeDuration: math.MaxInt64, } @@ -2274,7 +2274,7 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { 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) - cfg := &config.Config{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), MaxStakeDuration: math.MaxInt64, } @@ -2318,7 +2318,7 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { env.state.EXPECT().DeleteUTXO(gomock.Any()).Times(len(env.unsignedTx.Ins)) env.state.EXPECT().AddUTXO(gomock.Any()).Times(len(env.unsignedTx.Outs)) - cfg := &config.Config{ + cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), MaxStakeDuration: math.MaxInt64, } @@ -2364,7 +2364,7 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { var ( ctx = snowtest.Context(t, constants.PlatformChainID) - defaultConfig = &config.Config{ + defaultConfig = &config.Internal{ DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), @@ -2426,7 +2426,7 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { { name: "invalid prior to E-Upgrade", updateExecutor: func(e *standardTxExecutor) error { - e.backend.Config = &config.Config{ + e.backend.Config = &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.Durango), } return nil @@ -2497,7 +2497,7 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { { name: "too many active validators", updateExecutor: func(e *standardTxExecutor) error { - e.backend.Config = &config.Config{ + e.backend.Config = &config.Internal{ DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, ValidatorFeeConfig: validatorfee.Config{ Capacity: 0, @@ -2687,7 +2687,7 @@ func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { var ( ctx = snowtest.Context(t, constants.PlatformChainID) - defaultConfig = &config.Config{ + defaultConfig = &config.Internal{ DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), @@ -2852,7 +2852,7 @@ func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { { name: "invalid prior to E-Upgrade", updateExecutor: func(e *standardTxExecutor) error { - e.backend.Config = &config.Config{ + e.backend.Config = &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.Durango), } return nil @@ -3066,7 +3066,7 @@ func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { name: "too many active validators", balance: 1, updateExecutor: func(e *standardTxExecutor) error { - e.backend.Config = &config.Config{ + e.backend.Config = &config.Internal{ DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, ValidatorFeeConfig: validatorfee.Config{ Capacity: 0, diff --git a/vms/platformvm/txs/executor/state_changes_test.go b/vms/platformvm/txs/executor/state_changes_test.go index acd997e1bdf0..339894e57100 100644 --- a/vms/platformvm/txs/executor/state_changes_test.go +++ b/vms/platformvm/txs/executor/state_changes_test.go @@ -95,7 +95,7 @@ func TestAdvanceTimeTo_UpdatesFeeState(t *testing.T) { validatorsModified, err := AdvanceTimeTo( &Backend{ - Config: &config.Config{ + Config: &config.Internal{ DynamicFeeConfig: feeConfig, UpgradeConfig: upgradetest.GetConfig(test.fork), }, @@ -207,7 +207,7 @@ func TestAdvanceTimeTo_RemovesStaleExpiries(t *testing.T) { validatorsModified, err := AdvanceTimeTo( &Backend{ - Config: &config.Config{ + Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), }, }, @@ -257,7 +257,7 @@ func TestAdvanceTimeTo_UpdateSoVs(t *testing.T) { currentTime = genesistest.DefaultValidatorStartTime newTime = currentTime.Add(timeToAdvance) - config = config.Config{ + config = config.Internal{ ValidatorFeeConfig: fee.Config{ Capacity: genesis.LocalParams.ValidatorFeeConfig.Capacity, Target: 1, diff --git a/vms/platformvm/txs/txstest/context.go b/vms/platformvm/txs/txstest/context.go index 4fcc0f790ec8..0e8709b4e05a 100644 --- a/vms/platformvm/txs/txstest/context.go +++ b/vms/platformvm/txs/txstest/context.go @@ -13,7 +13,7 @@ import ( func newContext( ctx *snow.Context, - config *config.Config, + config *config.Internal, state state.State, ) *builder.Context { var ( diff --git a/vms/platformvm/txs/txstest/wallet.go b/vms/platformvm/txs/txstest/wallet.go index 0a040eed7d82..f26f2d612aef 100644 --- a/vms/platformvm/txs/txstest/wallet.go +++ b/vms/platformvm/txs/txstest/wallet.go @@ -28,7 +28,7 @@ import ( func NewWallet( t testing.TB, ctx *snow.Context, - config *config.Config, + config *config.Internal, state state.State, kc *secp256k1fx.Keychain, subnetIDs []ids.ID, diff --git a/vms/platformvm/validator_set_property_test.go b/vms/platformvm/validator_set_property_test.go index 9a3e84392724..7189778012d1 100644 --- a/vms/platformvm/validator_set_property_test.go +++ b/vms/platformvm/validator_set_property_test.go @@ -259,7 +259,7 @@ func addSubnetValidator(t testing.TB, vm *VM, data *validatorInputData, subnetID NodeID: data.nodeID, Start: uint64(data.startTime.Unix()), End: uint64(data.endTime.Unix()), - Wght: vm.Config.MinValidatorStake, + Wght: vm.Internal.MinValidatorStake, }, Subnet: subnetID, }, @@ -290,7 +290,7 @@ func addPrimaryValidatorWithBLSKey(t testing.TB, vm *VM, data *validatorInputDat NodeID: data.nodeID, Start: uint64(data.startTime.Unix()), End: uint64(data.endTime.Unix()), - Wght: vm.Config.MinValidatorStake, + Wght: vm.Internal.MinValidatorStake, }, Subnet: constants.PrimaryNetworkID, }, @@ -620,7 +620,7 @@ func TestTimestampListGenerator(t *testing.T) { // add a single validator at the end of times, // to make sure it won't pollute our tests func buildVM(t *testing.T) (*VM, ids.ID, error) { - vm := &VM{Config: config.Config{ + vm := &VM{Internal: config.Internal{ Chains: chains.TestManager, UptimeLockedCalculator: uptime.NewLockedCalculator(), SybilProtectionEnabled: true, diff --git a/vms/platformvm/validators/manager.go b/vms/platformvm/validators/manager.go index be37e6ee73e2..5d0c6c803c65 100644 --- a/vms/platformvm/validators/manager.go +++ b/vms/platformvm/validators/manager.go @@ -104,7 +104,7 @@ type State interface { func NewManager( log logging.Logger, - cfg config.Config, + cfg config.Internal, state State, metrics metrics.Metrics, clk *mockable.Clock, @@ -131,7 +131,7 @@ func NewManager( // calling exported functions. type manager struct { log logging.Logger - cfg config.Config + cfg config.Internal state State metrics metrics.Metrics clk *mockable.Clock diff --git a/vms/platformvm/validators/manager_benchmark_test.go b/vms/platformvm/validators/manager_benchmark_test.go index 0b2222f45f1a..fa441db708e3 100644 --- a/vms/platformvm/validators/manager_benchmark_test.go +++ b/vms/platformvm/validators/manager_benchmark_test.go @@ -60,7 +60,7 @@ func BenchmarkGetValidatorSet(b *testing.B) { m := NewManager( logging.NoLog{}, - config.Config{ + config.Internal{ Validators: vdrs, }, s, diff --git a/vms/platformvm/validators/manager_test.go b/vms/platformvm/validators/manager_test.go index 525d7b3646a4..f5893cd40870 100644 --- a/vms/platformvm/validators/manager_test.go +++ b/vms/platformvm/validators/manager_test.go @@ -100,7 +100,7 @@ func TestGetValidatorSet_AfterEtna(t *testing.T) { m := NewManager( logging.NoLog{}, - config.Config{ + config.Internal{ Validators: vdrs, }, s, diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index 975e13f7ebd5..7006c02d17ba 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -58,7 +58,7 @@ var ( ) type VM struct { - config.Config + config.Internal blockbuilder.Builder *network.Network validators.State @@ -105,7 +105,7 @@ func (vm *VM) Initialize( ) error { chainCtx.Log.Verbo("initializing platform chain") - execConfig, err := config.GetExecutionConfig(configBytes) + execConfig, err := config.GetConfig(configBytes) if err != nil { return err } @@ -138,8 +138,8 @@ func (vm *VM) Initialize( vm.db, genesisBytes, registerer, - vm.Config.Validators, - vm.Config.UpgradeConfig, + vm.Internal.Validators, + vm.Internal.UpgradeConfig, execConfig, vm.ctx, vm.metrics, @@ -149,14 +149,14 @@ func (vm *VM) Initialize( return err } - validatorManager := pvalidators.NewManager(chainCtx.Log, vm.Config, vm.state, vm.metrics, &vm.clock) + validatorManager := pvalidators.NewManager(chainCtx.Log, vm.Internal, vm.state, vm.metrics, &vm.clock) vm.State = validatorManager utxoVerifier := utxo.NewVerifier(vm.ctx, &vm.clock, vm.fx) vm.uptimeManager = uptime.NewManager(vm.state, &vm.clock) vm.UptimeLockedCalculator.SetCalculator(&vm.bootstrapped, &chainCtx.Lock, vm.uptimeManager) txExecutorBackend := &txexecutor.Backend{ - Config: &vm.Config, + Config: &vm.Internal, Ctx: vm.ctx, Clk: &vm.clock, Fx: vm.fx, @@ -288,7 +288,7 @@ func (vm *VM) pruneMempool() error { // Create all chains that exist that this node validates. func (vm *VM) initBlockchains() error { - if vm.Config.PartialSyncPrimaryNetwork { + if vm.Internal.PartialSyncPrimaryNetwork { vm.ctx.Log.Info("skipping primary network chain creation") } else if err := vm.createSubnet(constants.PrimaryNetworkID); err != nil { return err @@ -325,7 +325,7 @@ func (vm *VM) createSubnet(subnetID ids.ID) error { if !ok { return fmt.Errorf("expected tx type *txs.CreateChainTx but got %T", chain.Unsigned) } - vm.Config.CreateChain(chain.ID(), tx) + vm.Internal.CreateChain(chain.ID(), tx) } return nil } diff --git a/vms/platformvm/vm_regression_test.go b/vms/platformvm/vm_regression_test.go index 9a3c440e75c6..ad6da320b857 100644 --- a/vms/platformvm/vm_regression_test.go +++ b/vms/platformvm/vm_regression_test.go @@ -331,7 +331,7 @@ func TestUnverifiedParentPanicRegression(t *testing.T) { baseDB := memdb.New() atomicDB := prefixdb.New([]byte{1}, baseDB) - vm := &VM{Config: config.Config{ + vm := &VM{Internal: config.Internal{ Chains: chains.TestManager, Validators: validators.NewManager(), UptimeLockedCalculator: uptime.NewLockedCalculator(), @@ -631,13 +631,13 @@ func TestRejectedStateRegressionInvalidValidatorTimestamp(t *testing.T) { } // Force a reload of the state from the database. - vm.Config.Validators = validators.NewManager() + vm.Internal.Validators = validators.NewManager() newState := statetest.New(t, statetest.Config{ DB: vm.db, - Validators: vm.Config.Validators, - Upgrades: vm.Config.UpgradeConfig, + Validators: vm.Internal.Validators, + Upgrades: vm.Internal.UpgradeConfig, Context: vm.ctx, - Rewards: reward.NewCalculator(vm.Config.RewardConfig), + Rewards: reward.NewCalculator(vm.Internal.RewardConfig), }) // Verify that new validator is now in the current validator set. @@ -923,13 +923,13 @@ func TestRejectedStateRegressionInvalidValidatorReward(t *testing.T) { } // Force a reload of the state from the database. - vm.Config.Validators = validators.NewManager() + vm.Internal.Validators = validators.NewManager() newState := statetest.New(t, statetest.Config{ DB: vm.db, - Validators: vm.Config.Validators, - Upgrades: vm.Config.UpgradeConfig, + Validators: vm.Internal.Validators, + Upgrades: vm.Internal.UpgradeConfig, Context: vm.ctx, - Rewards: reward.NewCalculator(vm.Config.RewardConfig), + Rewards: reward.NewCalculator(vm.Internal.RewardConfig), }) // Verify that validators are in the current validator set with the correct diff --git a/vms/platformvm/vm_test.go b/vms/platformvm/vm_test.go index f377fd31f894..e8c83ba3350e 100644 --- a/vms/platformvm/vm_test.go +++ b/vms/platformvm/vm_test.go @@ -128,7 +128,7 @@ func defaultVM(t *testing.T, f upgradetest.Fork) (*VM, database.Database, *mutab // always reset latestForkTime (a package level variable) // to ensure test independence latestForkTime = genesistest.DefaultValidatorStartTime.Add(time.Second) - vm := &VM{Config: config.Config{ + vm := &VM{Internal: config.Internal{ Chains: chains.TestManager, UptimeLockedCalculator: uptime.NewLockedCalculator(), SybilProtectionEnabled: true, @@ -237,7 +237,7 @@ func newWallet(t testing.TB, vm *VM, c walletConfig) wallet.Wallet { return txstest.NewWallet( t, vm.ctx, - &vm.Config, + &vm.Internal, vm.state, secp256k1fx.NewKeychain(c.keys...), c.subnetIDs, @@ -1018,7 +1018,7 @@ func TestRestartFullyAccepted(t *testing.T) { db := memdb.New() firstDB := prefixdb.New([]byte{}, db) - firstVM := &VM{Config: config.Config{ + firstVM := &VM{Internal: config.Internal{ Chains: chains.TestManager, Validators: validators.NewManager(), UptimeLockedCalculator: uptime.NewLockedCalculator(), @@ -1103,7 +1103,7 @@ func TestRestartFullyAccepted(t *testing.T) { require.NoError(firstVM.Shutdown(context.Background())) firstCtx.Lock.Unlock() - secondVM := &VM{Config: config.Config{ + secondVM := &VM{Internal: config.Internal{ Chains: chains.TestManager, Validators: validators.NewManager(), UptimeLockedCalculator: uptime.NewLockedCalculator(), @@ -1149,7 +1149,7 @@ func TestBootstrapPartiallyAccepted(t *testing.T) { vmDB := prefixdb.New(chains.VMDBPrefix, baseDB) bootstrappingDB := prefixdb.New(chains.ChainBootstrappingDBPrefix, baseDB) - vm := &VM{Config: config.Config{ + vm := &VM{Internal: config.Internal{ Chains: chains.TestManager, Validators: validators.NewManager(), UptimeLockedCalculator: uptime.NewLockedCalculator(), @@ -1497,7 +1497,7 @@ func TestBootstrapPartiallyAccepted(t *testing.T) { func TestUnverifiedParent(t *testing.T) { require := require.New(t) - vm := &VM{Config: config.Config{ + vm := &VM{Internal: config.Internal{ Chains: chains.TestManager, Validators: validators.NewManager(), UptimeLockedCalculator: uptime.NewLockedCalculator(), @@ -1654,7 +1654,7 @@ func TestUptimeDisallowedWithRestart(t *testing.T) { firstDB := prefixdb.New([]byte{}, db) const firstUptimePercentage = 20 // 20% - firstVM := &VM{Config: config.Config{ + firstVM := &VM{Internal: config.Internal{ Chains: chains.TestManager, UptimePercentage: firstUptimePercentage / 100., RewardConfig: defaultRewardConfig, @@ -1701,7 +1701,7 @@ func TestUptimeDisallowedWithRestart(t *testing.T) { // Restart the VM with a larger uptime requirement secondDB := prefixdb.New([]byte{}, db) const secondUptimePercentage = 21 // 21% > firstUptimePercentage, so uptime for reward is not met now - secondVM := &VM{Config: config.Config{ + secondVM := &VM{Internal: config.Internal{ Chains: chains.TestManager, UptimePercentage: secondUptimePercentage / 100., Validators: validators.NewManager(), @@ -1797,7 +1797,7 @@ func TestUptimeDisallowedAfterNeverConnecting(t *testing.T) { db := memdb.New() - vm := &VM{Config: config.Config{ + vm := &VM{Internal: config.Internal{ Chains: chains.TestManager, UptimePercentage: .2, RewardConfig: defaultRewardConfig, From 57974428de19917b1dfc5fd627bc0584000688e0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 11 Nov 2024 09:56:18 -0500 Subject: [PATCH 379/400] merge --- vms/platformvm/txs/executor/standard_tx_executor_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 859dd88d32a7..117938e3e60c 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -3219,7 +3219,7 @@ func TestStandardExecutorSetSubnetValidatorWeightTx(t *testing.T) { var ( ctx = snowtest.Context(t, constants.PlatformChainID) - defaultConfig = &config.Config{ + defaultConfig = &config.Internal{ DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), @@ -3396,7 +3396,7 @@ func TestStandardExecutorSetSubnetValidatorWeightTx(t *testing.T) { { name: "invalid prior to E-Upgrade", updateExecutor: func(e *standardTxExecutor) error { - e.backend.Config = &config.Config{ + e.backend.Config = &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.Durango), } return nil From 22e6b6b8f6ad754613e5d7379ec0c981ef7531d3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 11 Nov 2024 10:35:28 -0500 Subject: [PATCH 380/400] cleanup code --- vms/platformvm/block/executor/block.go | 52 +++++++++++--------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/vms/platformvm/block/executor/block.go b/vms/platformvm/block/executor/block.go index 0c3cffc89954..b5f7e46308bf 100644 --- a/vms/platformvm/block/executor/block.go +++ b/vms/platformvm/block/executor/block.go @@ -31,41 +31,33 @@ func (*Block) ShouldVerifyWithContext(context.Context) (bool, error) { func (b *Block) VerifyWithContext(ctx context.Context, blockContext *smblock.Context) error { blkID := b.ID() - if blkState, ok := b.manager.blkIDToState[blkID]; ok { - if !blkState.verifiedHeights.Contains(blockContext.PChainHeight) { - // Only the validity of warp messages need to be verified because - // this block was already executed with a different block context. - err := VerifyWarpMessages( - ctx, - b.manager.ctx.NetworkID, - b.manager.ctx.ValidatorState, - blockContext.PChainHeight, - b, - ) - if err != nil { - return err - } - - blkState.verifiedHeights.Add(blockContext.PChainHeight) + blkState, previouslyExecuted := b.manager.blkIDToState[blkID] + warpAlreadyVerified := previouslyExecuted && blkState.verifiedHeights.Contains(blockContext.PChainHeight) + + // If the chain is bootstrapped and the warp messages haven't been verified, + // we must verify them. + if !warpAlreadyVerified && b.manager.txExecutorBackend.Bootstrapped.Get() { + err := VerifyWarpMessages( + ctx, + b.manager.ctx.NetworkID, + b.manager.ctx.ValidatorState, + blockContext.PChainHeight, + b, + ) + if err != nil { + return err } - - 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, - blockContext.PChainHeight, - b, - ) - if err != nil { - return err + // If the block was previously executed, we don't need to execute it again, + // we can just mark that the warp messages are valid at this height. + if previouslyExecuted { + blkState.verifiedHeights.Add(blockContext.PChainHeight) + return nil } - // Since the warp messages are valid, we need to execute the rest of the - // validity checks. + // Since this is the first time we are verifying this block, we must execute + // the state transitions to generate the state diffs. return b.Visit(&verifier{ backend: b.manager.backend, txExecutorBackend: b.manager.txExecutorBackend, From d92a83db63be1cbd8f41c833543a1cc6098b9787 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 11 Nov 2024 10:44:17 -0500 Subject: [PATCH 381/400] fix unit tests --- vms/platformvm/block/executor/verifier_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 16b129b36882..26023c36718a 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -463,7 +463,8 @@ func TestVerifierVisitCommitBlock(t *testing.T) { Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), }, - Clk: &mockable.Clock{}, + Clk: &mockable.Clock{}, + Bootstrapped: utils.NewAtomic(true), }, backend: backend, } @@ -530,7 +531,8 @@ func TestVerifierVisitAbortBlock(t *testing.T) { Config: &config.Internal{ UpgradeConfig: upgradetest.GetConfig(upgradetest.ApricotPhasePost6), }, - Clk: &mockable.Clock{}, + Clk: &mockable.Clock{}, + Bootstrapped: utils.NewAtomic(true), }, backend: backend, } From 80078959e9521b3fa2dcb3e270fb1e141c8cea87 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 11 Nov 2024 11:09:11 -0500 Subject: [PATCH 382/400] nit comment --- vms/platformvm/network/warp.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/network/warp.go b/vms/platformvm/network/warp.go index 339de22e073e..7935eb7b6d0b 100644 --- a/vms/platformvm/network/warp.go +++ b/vms/platformvm/network/warp.go @@ -213,10 +213,7 @@ func (s signatureRequestVerifier) verifySubnetValidatorNotCurrentlyRegistered( s.stateLock.Lock() defer s.stateLock.Unlock() - // Verify that the provided validationID either: - // - Is in the current state - // - Was removed from the current state - // - Was not included in the subnet conversion + // Verify that the provided subnetID has been converted. _, err = s.state.GetSubnetConversion(subnetID) if err == database.ErrNotFound { return &common.AppError{ @@ -246,7 +243,8 @@ func (s signatureRequestVerifier) verifySubnetValidatorNotCurrentlyRegistered( } } - // Either the validator was removed or it was never registered. + // Either the validator was removed or it was never registered as part of + // the subnet conversion. return nil } From 024b0421fa9d9f0c8e2d6a7fb7153c94ef99f5e1 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 11 Nov 2024 12:33:09 -0500 Subject: [PATCH 383/400] nit renames --- .../txs/executor/standard_tx_executor_test.go | 14 +++++++------- 1 file changed, 7 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 117938e3e60c..80f9ec065d7d 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -3273,12 +3273,12 @@ func TestStandardExecutorSetSubnetValidatorWeightTx(t *testing.T) { require.NoError(t, err) // Create the subnet conversion - initialSK, err := bls.NewSecretKey() + sk, err := bls.NewSecretKey() require.NoError(t, err) const ( - initialWeight = 1 - initialBalance = units.Avax + initialWeight = 1 + balance = units.Avax ) var ( subnetID = createSubnetTx.ID() @@ -3287,8 +3287,8 @@ func TestStandardExecutorSetSubnetValidatorWeightTx(t *testing.T) { validator = &txs.ConvertSubnetValidator{ NodeID: ids.GenerateTestNodeID().Bytes(), Weight: initialWeight, - Balance: initialBalance, - Signer: *signer.NewProofOfPossession(initialSK), + Balance: balance, + Signer: *signer.NewProofOfPossession(sk), // RemainingBalanceOwner and DeactivationOwner are initialized so // that later reflect based equality checks pass. RemainingBalanceOwner: message.PChainOwner{ @@ -3348,7 +3348,7 @@ func TestStandardExecutorSetSubnetValidatorWeightTx(t *testing.T) { Signers: set.NewBits(0).Bytes(), Signature: ([bls.SignatureLen]byte)(bls.SignatureToBytes( bls.Sign( - initialSK, + sk, unsignedIncreaseWeightWarpMessage.Bytes(), ), )), @@ -3599,7 +3599,7 @@ func TestStandardExecutorSetSubnetValidatorWeightTx(t *testing.T) { ID: ctx.AVAXAssetID, }, Out: &secp256k1fx.TransferOutput{ - Amt: initialBalance, + Amt: balance, OutputOwners: secp256k1fx.OutputOwners{ Threshold: validator.RemainingBalanceOwner.Threshold, Addrs: validator.RemainingBalanceOwner.Addresses, From 5b832d8d372f5e9d62a0848b1e0825e2c8705c82 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 11 Nov 2024 13:20:37 -0500 Subject: [PATCH 384/400] address testing comments --- tests/e2e/p/l1.go | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 834f882275f5..8c6726ab6744 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -231,10 +231,15 @@ var _ = e2e.DescribePChain("[L1]", func() { client = platformvm.NewClient(subnetGenesisNode.URI) txID = tx.ID() ) - require.Eventually(func() bool { - _, err := client.GetTx(tc.DefaultContext(), txID) - return err == nil - }, tests.DefaultTimeout, 100*time.Millisecond) + tc.Eventually( + func() bool { + _, err := client.GetTx(tc.DefaultContext(), txID) + return err == nil + }, + tests.DefaultTimeout, + e2e.DefaultPollingInterval, + "transaction not accepted", + ) }) }) @@ -397,10 +402,15 @@ var _ = e2e.DescribePChain("[L1]", func() { client = platformvm.NewClient(subnetGenesisNode.URI) txID = tx.ID() ) - require.Eventually(func() bool { - _, err := client.GetTx(tc.DefaultContext(), txID) - return err == nil - }, tests.DefaultTimeout, 100*time.Millisecond) + tc.Eventually( + func() bool { + _, err := client.GetTx(tc.DefaultContext(), txID) + return err == nil + }, + tests.DefaultTimeout, + e2e.DefaultPollingInterval, + "transaction not accepted", + ) }) }) }) @@ -506,10 +516,15 @@ var _ = e2e.DescribePChain("[L1]", func() { client = platformvm.NewClient(subnetGenesisNode.URI) txID = tx.ID() ) - require.Eventually(func() bool { - _, err := client.GetTx(tc.DefaultContext(), txID) - return err == nil - }, tests.DefaultTimeout, 100*time.Millisecond) + tc.Eventually( + func() bool { + _, err := client.GetTx(tc.DefaultContext(), txID) + return err == nil + }, + tests.DefaultTimeout, + e2e.DefaultPollingInterval, + "transaction not accepted", + ) }) }) From 33c2ac89a27b98e49f8c1d564ed0f3824c8df085 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 11 Nov 2024 15:24:05 -0500 Subject: [PATCH 385/400] nit --- vms/platformvm/config/config.go | 8 ++++---- vms/platformvm/config/config_test.go | 2 +- vms/platformvm/state/statetest/state.go | 24 ++++++++++++------------ 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/vms/platformvm/config/config.go b/vms/platformvm/config/config.go index f66d3c69fa32..9d6184b3747d 100644 --- a/vms/platformvm/config/config.go +++ b/vms/platformvm/config/config.go @@ -47,13 +47,13 @@ type Config struct { MempoolPruneFrequency time.Duration `json:"mempool-prune-frequency"` } -// GetConfig returns an ExecutionConfig -// input is unmarshalled into an ExecutionConfig previously -// initialized with default values +// GetConfig returns a Config from the provided json encoded bytes. If a +// configuration is not provided in the bytes, the default value is set. If +// empty bytes are provided, the default config is returned. func GetConfig(b []byte) (*Config, error) { ec := Default - // if bytes are empty keep default values + // An empty slice is invalid json, so handle that as a special case. if len(b) == 0 { return &ec, nil } diff --git a/vms/platformvm/config/config_test.go b/vms/platformvm/config/config_test.go index a9ad976bb559..14b9b0e4d178 100644 --- a/vms/platformvm/config/config_test.go +++ b/vms/platformvm/config/config_test.go @@ -29,7 +29,7 @@ func verifyInitializedStruct(tb testing.TB, s interface{}) { } } -func TestExecutionConfigUnmarshal(t *testing.T) { +func TestConfigUnmarshal(t *testing.T) { t.Run("default values from empty json", func(t *testing.T) { require := require.New(t) b := []byte(`{}`) diff --git a/vms/platformvm/state/statetest/state.go b/vms/platformvm/state/statetest/state.go index 0aa6c631b0d2..a5807e070c03 100644 --- a/vms/platformvm/state/statetest/state.go +++ b/vms/platformvm/state/statetest/state.go @@ -30,15 +30,15 @@ import ( var DefaultNodeID = ids.GenerateTestNodeID() type Config struct { - DB database.Database - Genesis []byte - Registerer prometheus.Registerer - Validators validators.Manager - Upgrades upgrade.Config - ExecutionConfig config.Config - Context *snow.Context - Metrics metrics.Metrics - Rewards reward.Calculator + DB database.Database + Genesis []byte + Registerer prometheus.Registerer + Validators validators.Manager + Upgrades upgrade.Config + Config config.Config + Context *snow.Context + Metrics metrics.Metrics + Rewards reward.Calculator } func New(t testing.TB, c Config) state.State { @@ -57,8 +57,8 @@ func New(t testing.TB, c Config) state.State { if c.Upgrades == (upgrade.Config{}) { c.Upgrades = upgradetest.GetConfig(upgradetest.Latest) } - if c.ExecutionConfig == (config.Config{}) { - c.ExecutionConfig = config.Default + if c.Config == (config.Config{}) { + c.Config = config.Default } if c.Context == nil { c.Context = &snow.Context{ @@ -85,7 +85,7 @@ func New(t testing.TB, c Config) state.State { c.Registerer, c.Validators, c.Upgrades, - &c.ExecutionConfig, + &c.Config, c.Context, c.Metrics, c.Rewards, From 860fc32c5f61a3bbefab78af4ca872bb2809bd5c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 11 Nov 2024 18:15:14 -0500 Subject: [PATCH 386/400] Refactor subnet auth verification --- .../txs/executor/staker_tx_verification.go | 6 +-- .../txs/executor/standard_tx_executor.go | 6 +-- .../txs/executor/standard_tx_executor_test.go | 18 +++++--- .../txs/executor/subnet_tx_verification.go | 44 ++++++++++++------- 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/vms/platformvm/txs/executor/staker_tx_verification.go b/vms/platformvm/txs/executor/staker_tx_verification.go index b84ad9d6e2b1..34a14eb5ce9c 100644 --- a/vms/platformvm/txs/executor/staker_tx_verification.go +++ b/vms/platformvm/txs/executor/staker_tx_verification.go @@ -252,7 +252,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 } @@ -332,7 +332,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 } @@ -781,7 +781,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 b64972452eba..f6eef4df867c 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -197,7 +197,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.backend.Fx, e.state, e.tx, tx.SubnetID, tx.SubnetAuth) if err != nil { return err } @@ -510,7 +510,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.backend.Fx, e.state, e.tx, tx.Subnet, tx.SubnetAuth) if err != nil { return err } @@ -689,7 +689,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.backend.Fx, e.state, e.tx, tx.Subnet, tx.SubnetAuth) if err != nil { return err } diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 394ff2b54f8c..2f43ca373613 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -1873,14 +1873,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.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), @@ -1900,16 +1899,19 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { } 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).AnyTimes() cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), @@ -1929,7 +1931,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { } return env.unsignedTx, e }, - expectedErr: database.ErrNotFound, + expectedErr: errWrongNumberOfCredentials, }, { name: "no permission to remove validator", @@ -2190,7 +2192,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).AnyTimes() cfg := &config.Internal{ UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), diff --git a/vms/platformvm/txs/executor/subnet_tx_verification.go b/vms/platformvm/txs/executor/subnet_tx_verification.go index 59acbe650491..f58c230d5e37 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 } @@ -55,32 +56,45 @@ func verifyPoASubnetAuthorization( } // verifySubnetAuthorization carries out the validation for modifying a subnet. -// The last credential in [sTx.Creds] is used as the subnet authorization. +// The last credential in [tx.Creds] is used as the subnet authorization. // 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 [tx.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 } From cdabac6e93558a41d02cf336f811618a398762c2 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 12 Nov 2024 15:34:39 -0500 Subject: [PATCH 387/400] Add platform.getSubnetOnlyValidator API --- tests/e2e/p/l1.go | 84 +++++++++++++++++++++++++++++++++++++++ vms/platformvm/client.go | 67 +++++++++++++++++++++++++++++++ vms/platformvm/service.go | 79 ++++++++++++++++++++++++++++++++++++ 3 files changed, 230 insertions(+) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 96156ddff36b..308ce9acd4e6 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -224,6 +224,7 @@ var _ = e2e.DescribePChain("[L1]", func() { ) require.NoError(err) }) + genesisValidationID := subnetID.Append(0) tc.By("verifying the Permissioned Subnet was converted to an L1", func() { expectedConversionID, err := warpmessage.SubnetConversionID(warpmessage.SubnetConversionData{ @@ -267,6 +268,32 @@ var _ = e2e.DescribePChain("[L1]", func() { }, }) }) + + tc.By("verifying the SoV can be fetched", func() { + sov, _, err := pClient.GetSubnetOnlyValidator(tc.DefaultContext(), genesisValidationID) + require.NoError(err) + require.Equal( + platformvm.SubnetOnlyValidator{ + SubnetID: subnetID, + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + DeactivationOwner: &secp256k1fx.OutputOwners{}, + Weight: genesisWeight, + MinNonce: 0, + }, + platformvm.SubnetOnlyValidator{ + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: sov.PublicKey, + RemainingBalanceOwner: sov.RemainingBalanceOwner, + DeactivationOwner: sov.DeactivationOwner, + Weight: sov.Weight, + MinNonce: sov.MinNonce, + }, + ) + require.LessOrEqual(sov.Balance, genesisBalance) + }) }) advanceProposerVMPChainHeight := func() { @@ -284,6 +311,9 @@ var _ = e2e.DescribePChain("[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) @@ -365,6 +395,33 @@ var _ = e2e.DescribePChain("[L1]", func() { }, }) }) + + tc.By("verifying the SoV can be fetched", func() { + sov, _, err := pClient.GetSubnetOnlyValidator(tc.DefaultContext(), registerValidationID) + require.NoError(err) + require.Equal( + platformvm.SubnetOnlyValidator{ + SubnetID: subnetID, + NodeID: subnetRegisterNode.NodeID, + PublicKey: registerNodePK, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + DeactivationOwner: &secp256k1fx.OutputOwners{}, + Weight: registerWeight, + MinNonce: 0, + Balance: 0, + }, + platformvm.SubnetOnlyValidator{ + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: sov.PublicKey, + RemainingBalanceOwner: sov.RemainingBalanceOwner, + DeactivationOwner: sov.DeactivationOwner, + Weight: sov.Weight, + MinNonce: sov.MinNonce, + Balance: sov.Balance, + }, + ) + }) }) var nextNonce uint64 @@ -438,6 +495,33 @@ var _ = e2e.DescribePChain("[L1]", func() { }, }) }) + + tc.By("verifying the SoV can be fetched", func() { + sov, _, err := pClient.GetSubnetOnlyValidator(tc.DefaultContext(), registerValidationID) + require.NoError(err) + require.Equal( + platformvm.SubnetOnlyValidator{ + SubnetID: subnetID, + NodeID: subnetRegisterNode.NodeID, + PublicKey: registerNodePK, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, + DeactivationOwner: &secp256k1fx.OutputOwners{}, + Weight: updatedWeight, + MinNonce: nextNonce, + Balance: 0, + }, + platformvm.SubnetOnlyValidator{ + SubnetID: sov.SubnetID, + NodeID: sov.NodeID, + PublicKey: sov.PublicKey, + RemainingBalanceOwner: sov.RemainingBalanceOwner, + DeactivationOwner: sov.DeactivationOwner, + Weight: sov.Weight, + MinNonce: sov.MinNonce, + Balance: sov.Balance, + }, + ) + }) }) tc.By("advancing the proposervm P-chain height", advanceProposerVMPChainHeight) diff --git a/vms/platformvm/client.go b/vms/platformvm/client.go index 505c545bdded..0433e22ba10c 100644 --- a/vms/platformvm/client.go +++ b/vms/platformvm/client.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" "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/formatting" "github.com/ava-labs/avalanchego/utils/formatting/address" @@ -71,6 +72,9 @@ type Client interface { GetStakingAssetID(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (ids.ID, error) // GetCurrentValidators returns the list of current validators for subnet with ID [subnetID] GetCurrentValidators(ctx context.Context, subnetID ids.ID, nodeIDs []ids.NodeID, options ...rpc.Option) ([]ClientPermissionlessValidator, error) + // GetSubnetOnlyValidator returns the requested SoV with [validationID] and + // the height at which it was calculated. + GetSubnetOnlyValidator(ctx context.Context, validationID ids.ID, options ...rpc.Option) (SubnetOnlyValidator, uint64, error) // GetCurrentSupply returns an upper bound on the supply of AVAX in the system along with the P-chain height GetCurrentSupply(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (uint64, uint64, error) // SampleValidators returns the nodeIDs of a sample of [sampleSize] validators from the current validator set for subnet with ID [subnetID] @@ -326,6 +330,69 @@ func (c *client) GetCurrentValidators( return getClientPermissionlessValidators(res.Validators) } +// SubnetOnlyValidator is the response from calling GetSubnetOnlyValidator on +// the API client. +type SubnetOnlyValidator struct { + SubnetID ids.ID + NodeID ids.NodeID + PublicKey *bls.PublicKey + RemainingBalanceOwner *secp256k1fx.OutputOwners + DeactivationOwner *secp256k1fx.OutputOwners + StartTime uint64 + Weight uint64 + MinNonce uint64 + // Balance is the remaining amount of AVAX this SoV has to pay the + // continuous fee. + Balance uint64 +} + +func (c *client) GetSubnetOnlyValidator( + ctx context.Context, + validationID ids.ID, + options ...rpc.Option, +) (SubnetOnlyValidator, uint64, error) { + res := &GetSubnetOnlyValidatorReply{} + err := c.requester.SendRequest(ctx, "platform.getSubnetOnlyValidator", &GetSubnetOnlyValidatorArgs{ + ValidationID: validationID, + }, res, options...) + if err != nil { + return SubnetOnlyValidator{}, 0, err + } + + pk, err := bls.PublicKeyFromCompressedBytes(res.PublicKey) + if err != nil { + return SubnetOnlyValidator{}, 0, err + } + remainingBalanceOwnerAddrs, err := address.ParseToIDs(res.RemainingBalanceOwner.Addresses) + if err != nil { + return SubnetOnlyValidator{}, 0, err + } + deactivationOwnerAddrs, err := address.ParseToIDs(res.DeactivationOwner.Addresses) + if err != nil { + return SubnetOnlyValidator{}, 0, err + } + + return SubnetOnlyValidator{ + SubnetID: res.SubnetID, + NodeID: res.NodeID, + PublicKey: pk, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{ + Locktime: uint64(res.RemainingBalanceOwner.Locktime), + Threshold: uint32(res.RemainingBalanceOwner.Threshold), + Addrs: remainingBalanceOwnerAddrs, + }, + DeactivationOwner: &secp256k1fx.OutputOwners{ + Locktime: uint64(res.DeactivationOwner.Locktime), + Threshold: uint32(res.DeactivationOwner.Threshold), + Addrs: deactivationOwnerAddrs, + }, + StartTime: uint64(res.StartTime), + Weight: uint64(res.Weight), + MinNonce: uint64(res.MinNonce), + Balance: uint64(res.Balance), + }, uint64(res.Height), err +} + func (c *client) GetCurrentSupply(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (uint64, uint64, error) { res := &GetCurrentSupplyReply{} err := c.requester.SendRequest(ctx, "platform.getCurrentSupply", &GetCurrentSupplyArgs{ diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index b7e9fad6e125..737efe0bff00 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -969,6 +969,85 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato return nil } +type GetSubnetOnlyValidatorArgs struct { + ValidationID ids.ID `json:"validationID"` +} + +type GetSubnetOnlyValidatorReply struct { + SubnetID ids.ID `json:"subnetID"` + NodeID ids.NodeID `json:"nodeID"` + // PublicKey is the compressed BLS public key of the validator + PublicKey types.JSONByteSlice `json:"publicKey"` + RemainingBalanceOwner platformapi.Owner `json:"remainingBalanceOwner"` + DeactivationOwner platformapi.Owner `json:"deactivationOwner"` + StartTime avajson.Uint64 `json:"startTime"` + Weight avajson.Uint64 `json:"weight"` + MinNonce avajson.Uint64 `json:"minNonce"` + // Balance is the remaining amount of AVAX this SoV has to pay the + // continuous fee, according to the last accepted state. + Balance avajson.Uint64 `json:"balance"` + // Height is the height of the last accepted block + Height avajson.Uint64 `json:"height"` +} + +// GetSubnetOnlyValidator returns the SoV if it exists +func (s *Service) GetSubnetOnlyValidator(r *http.Request, args *GetSubnetOnlyValidatorArgs, reply *GetSubnetOnlyValidatorReply) error { + s.vm.ctx.Log.Debug("API called", + zap.String("service", "platform"), + zap.String("method", "getSubnetOnlyValidator"), + ) + + s.vm.ctx.Lock.Lock() + defer s.vm.ctx.Lock.Unlock() + + sov, err := s.vm.state.GetSubnetOnlyValidator(args.ValidationID) + if err != nil { + return fmt.Errorf("fetching SoV %q failed: %w", args.ValidationID, err) + } + + var remainingBalanceOwner secp256k1fx.OutputOwners + if _, err := txs.Codec.Unmarshal(sov.RemainingBalanceOwner, &remainingBalanceOwner); err != nil { + return fmt.Errorf("failed unmarshalling remaining balance owner: %w", err) + } + remainingBalanceAPIOwner, err := s.getAPIOwner(&remainingBalanceOwner) + if err != nil { + return fmt.Errorf("failed formatting remaining balance owner: %w", err) + } + + var deactivationOwner secp256k1fx.OutputOwners + if _, err := txs.Codec.Unmarshal(sov.DeactivationOwner, &deactivationOwner); err != nil { + return fmt.Errorf("failed unmarshalling deactivation owner: %w", err) + } + deactivationAPIOwner, err := s.getAPIOwner(&deactivationOwner) + if err != nil { + return fmt.Errorf("failed formatting deactivation owner: %w", err) + } + + ctx := r.Context() + height, err := s.vm.GetCurrentHeight(ctx) + if err != nil { + return fmt.Errorf("failed getting current height: %w", err) + } + + reply.SubnetID = sov.SubnetID + reply.NodeID = sov.NodeID + reply.PublicKey = bls.PublicKeyToCompressedBytes( + bls.PublicKeyFromValidUncompressedBytes(sov.PublicKey), + ) + reply.RemainingBalanceOwner = *remainingBalanceAPIOwner + reply.DeactivationOwner = *deactivationAPIOwner + reply.StartTime = avajson.Uint64(sov.StartTime) + reply.Weight = avajson.Uint64(sov.Weight) + reply.MinNonce = avajson.Uint64(sov.MinNonce) + if sov.EndAccumulatedFee != 0 { + accruedFees := s.vm.state.GetAccruedFees() + reply.Balance = avajson.Uint64(sov.EndAccumulatedFee - accruedFees) + } + reply.Height = avajson.Uint64(height) + + return nil +} + // GetCurrentSupplyArgs are the arguments for calling GetCurrentSupply type GetCurrentSupplyArgs struct { SubnetID ids.ID `json:"subnetID"` From a513cf1c6ccbc2a19942c944db29169a8f40bc97 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 12 Nov 2024 15:37:16 -0500 Subject: [PATCH 388/400] nit cleanup test --- tests/e2e/p/l1.go | 41 +++++++++++------------------------------ 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 308ce9acd4e6..254a7369437e 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -272,6 +272,10 @@ var _ = e2e.DescribePChain("[L1]", func() { tc.By("verifying the SoV can be fetched", func() { sov, _, err := pClient.GetSubnetOnlyValidator(tc.DefaultContext(), genesisValidationID) require.NoError(err) + require.LessOrEqual(sov.Balance, genesisBalance) + + sov.StartTime = 0 + sov.Balance = 0 require.Equal( platformvm.SubnetOnlyValidator{ SubnetID: subnetID, @@ -282,17 +286,8 @@ var _ = e2e.DescribePChain("[L1]", func() { Weight: genesisWeight, MinNonce: 0, }, - platformvm.SubnetOnlyValidator{ - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: sov.PublicKey, - RemainingBalanceOwner: sov.RemainingBalanceOwner, - DeactivationOwner: sov.DeactivationOwner, - Weight: sov.Weight, - MinNonce: sov.MinNonce, - }, + sov, ) - require.LessOrEqual(sov.Balance, genesisBalance) }) }) @@ -399,6 +394,8 @@ var _ = e2e.DescribePChain("[L1]", func() { tc.By("verifying the SoV can be fetched", func() { sov, _, err := pClient.GetSubnetOnlyValidator(tc.DefaultContext(), registerValidationID) require.NoError(err) + + sov.StartTime = 0 require.Equal( platformvm.SubnetOnlyValidator{ SubnetID: subnetID, @@ -410,16 +407,7 @@ var _ = e2e.DescribePChain("[L1]", func() { MinNonce: 0, Balance: 0, }, - platformvm.SubnetOnlyValidator{ - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: sov.PublicKey, - RemainingBalanceOwner: sov.RemainingBalanceOwner, - DeactivationOwner: sov.DeactivationOwner, - Weight: sov.Weight, - MinNonce: sov.MinNonce, - Balance: sov.Balance, - }, + sov, ) }) }) @@ -499,6 +487,8 @@ var _ = e2e.DescribePChain("[L1]", func() { tc.By("verifying the SoV can be fetched", func() { sov, _, err := pClient.GetSubnetOnlyValidator(tc.DefaultContext(), registerValidationID) require.NoError(err) + + sov.StartTime = 0 require.Equal( platformvm.SubnetOnlyValidator{ SubnetID: subnetID, @@ -510,16 +500,7 @@ var _ = e2e.DescribePChain("[L1]", func() { MinNonce: nextNonce, Balance: 0, }, - platformvm.SubnetOnlyValidator{ - SubnetID: sov.SubnetID, - NodeID: sov.NodeID, - PublicKey: sov.PublicKey, - RemainingBalanceOwner: sov.RemainingBalanceOwner, - DeactivationOwner: sov.DeactivationOwner, - Weight: sov.Weight, - MinNonce: sov.MinNonce, - Balance: sov.Balance, - }, + sov, ) }) }) From 0b529af3af8dbae4b8702de849e0684ab0df2000 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 12 Nov 2024 15:52:27 -0500 Subject: [PATCH 389/400] wip --- RELEASES.md | 4 ++ vms/platformvm/service.md | 106 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index d5c4b453189e..2aa9d97d63a7 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,6 +2,10 @@ ## Pending Release +### APIs + +- Added `platform.getSubnetOnlyValidator` API + ### Configs - Added P-chain configs diff --git a/vms/platformvm/service.md b/vms/platformvm/service.md index dbb12907694b..ea7d3dac9fe7 100644 --- a/vms/platformvm/service.md +++ b/vms/platformvm/service.md @@ -780,6 +780,112 @@ curl -X POST --data '{ } ``` +### `platform.getSubnetOnlyValidator` + +Returns a current L1 validator. + +**Signature:** + +```sh +platform.getSubnetOnlyValidator({ + validationID: string, +}) -> { + subnetID: string, + nodeID: string, + publicKey: string, + remainingBalanceOwner: { + locktime: string, + threshold: string, + addresses: string[] + }, + deactivationOwner: { + locktime: string, + threshold: string, + addresses: string[] + }, + startTime: string, + weight: string, + minNonce: string, + balance: string, + height: string +} +``` + +- `subnetID` is the L1 this validator is validating. +- `nodeID` is the node ID of the validator. +- `publicKey` is the compressed BLS public key of the validator. +- `remainingBalanceOwner` is an `OutputOwners` which includes a `locktime`, `threshold`, and an array of `addresses`. It specifies the owner that will receive any withdrawn balance. +- `deactivationOwner` is an `OutputOwners` which includes a `locktime`, `threshold`, and an array of `addresses`. It specifies the owner that can withdraw the balance. +- `startTime` is the unix timestamp, in seconds, of when this validator was added to the validator set. +- `weight` is weight of this validator used for consensus voting and ICM. +- `minNonce` is minimum nonce that must be included in a `SetSubnetValidatorWeightTx` for the transaction to be valid. +- `balance` is current remaining balance that can be used to pay for the validators continuous fee. +- `height` is height of the last accepted block. + +**Example Call:** + +```sh +curl -X POST --data '{ + "jsonrpc": "2.0", + "method": "platform.getCurrentValidators", + "params": { + "nodeIDs": ["NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD"] + }, + "id": 1 +}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/P +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "result": { + "validators": [ + { + "txID": "2NNkpYTGfTFLSGXJcHtVv6drwVU2cczhmjK2uhvwDyxwsjzZMm", + "startTime": "1600368632", + "endTime": "1602960455", + "stakeAmount": "2000000000000", + "nodeID": "NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD", + "validationRewardOwner": { + "locktime": "0", + "threshold": "1", + "addresses": ["P-avax18jma8ppw3nhx5r4ap8clazz0dps7rv5ukulre5"] + }, + "delegationRewardOwner": { + "locktime": "0", + "threshold": "1", + "addresses": ["P-avax18jma8ppw3nhx5r4ap8clazz0dps7rv5ukulre5"] + }, + "potentialReward": "117431493426", + "delegationFee": "10.0000", + "uptime": "0.0000", + "connected": false, + "delegatorCount": "1", + "delegatorWeight": "25000000000", + "delegators": [ + { + "txID": "Bbai8nzGVcyn2VmeYcbS74zfjJLjDacGNVuzuvAQkHn1uWfoV", + "startTime": "1600368523", + "endTime": "1602960342", + "stakeAmount": "25000000000", + "nodeID": "NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD", + "rewardOwner": { + "locktime": "0", + "threshold": "1", + "addresses": ["P-avax18jma8ppw3nhx5r4ap8clazz0dps7rv5ukulre5"] + }, + "potentialReward": "11743144774" + } + ] + } + ] + }, + "id": 1 +} +``` + ### `platform.getHeight` Returns the height of the last accepted block. From 4935575cb2e34f4ae3d9574ce9b20e1bf0176d18 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 12 Nov 2024 16:05:24 -0500 Subject: [PATCH 390/400] nits --- vms/platformvm/service.go | 15 ++++++--- vms/platformvm/service.md | 66 +++++++++++++-------------------------- 2 files changed, 33 insertions(+), 48 deletions(-) diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 737efe0bff00..fc9f4b9ab4ac 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -37,6 +37,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/status" "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/vms/types" @@ -1005,20 +1006,26 @@ func (s *Service) GetSubnetOnlyValidator(r *http.Request, args *GetSubnetOnlyVal return fmt.Errorf("fetching SoV %q failed: %w", args.ValidationID, err) } - var remainingBalanceOwner secp256k1fx.OutputOwners + var remainingBalanceOwner message.PChainOwner if _, err := txs.Codec.Unmarshal(sov.RemainingBalanceOwner, &remainingBalanceOwner); err != nil { return fmt.Errorf("failed unmarshalling remaining balance owner: %w", err) } - remainingBalanceAPIOwner, err := s.getAPIOwner(&remainingBalanceOwner) + remainingBalanceAPIOwner, err := s.getAPIOwner(&secp256k1fx.OutputOwners{ + Threshold: remainingBalanceOwner.Threshold, + Addrs: remainingBalanceOwner.Addresses, + }) if err != nil { return fmt.Errorf("failed formatting remaining balance owner: %w", err) } - var deactivationOwner secp256k1fx.OutputOwners + var deactivationOwner message.PChainOwner if _, err := txs.Codec.Unmarshal(sov.DeactivationOwner, &deactivationOwner); err != nil { return fmt.Errorf("failed unmarshalling deactivation owner: %w", err) } - deactivationAPIOwner, err := s.getAPIOwner(&deactivationOwner) + deactivationAPIOwner, err := s.getAPIOwner(&secp256k1fx.OutputOwners{ + Threshold: deactivationOwner.Threshold, + Addrs: deactivationOwner.Addresses, + }) if err != nil { return fmt.Errorf("failed formatting deactivation owner: %w", err) } diff --git a/vms/platformvm/service.md b/vms/platformvm/service.md index ea7d3dac9fe7..0c192e1c7e46 100644 --- a/vms/platformvm/service.md +++ b/vms/platformvm/service.md @@ -827,9 +827,9 @@ platform.getSubnetOnlyValidator({ ```sh curl -X POST --data '{ "jsonrpc": "2.0", - "method": "platform.getCurrentValidators", + "method": "platform.getSubnetOnlyValidator", "params": { - "nodeIDs": ["NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD"] + "validationID": ["9FAftNgNBrzHUMMApsSyV6RcFiL9UmCbvsCu28xdLV2mQ7CMo"] }, "id": 1 }' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/P @@ -839,50 +839,28 @@ curl -X POST --data '{ ```json { - "jsonrpc": "2.0", - "result": { - "validators": [ - { - "txID": "2NNkpYTGfTFLSGXJcHtVv6drwVU2cczhmjK2uhvwDyxwsjzZMm", - "startTime": "1600368632", - "endTime": "1602960455", - "stakeAmount": "2000000000000", - "nodeID": "NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD", - "validationRewardOwner": { - "locktime": "0", - "threshold": "1", - "addresses": ["P-avax18jma8ppw3nhx5r4ap8clazz0dps7rv5ukulre5"] + "jsonrpc": "2.0", + "result": { + "subnetID": "2DeHa7Qb6sufPkmQcFWG2uCd4pBPv9WB6dkzroiMQhd1NSRtof", + "nodeID": "NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg", + "publicKey": "0x900c9b119b5c82d781d4b49be78c3fc7ae65f2b435b7ed9e3a8b9a03e475edff86d8a64827fec8db23a6f236afbf127d", + "remainingBalanceOwner": { + "locktime": "0", + "threshold": "0", + "addresses": [] }, - "delegationRewardOwner": { - "locktime": "0", - "threshold": "1", - "addresses": ["P-avax18jma8ppw3nhx5r4ap8clazz0dps7rv5ukulre5"] + "deactivationOwner": { + "locktime": "0", + "threshold": "0", + "addresses": [] }, - "potentialReward": "117431493426", - "delegationFee": "10.0000", - "uptime": "0.0000", - "connected": false, - "delegatorCount": "1", - "delegatorWeight": "25000000000", - "delegators": [ - { - "txID": "Bbai8nzGVcyn2VmeYcbS74zfjJLjDacGNVuzuvAQkHn1uWfoV", - "startTime": "1600368523", - "endTime": "1602960342", - "stakeAmount": "25000000000", - "nodeID": "NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD", - "rewardOwner": { - "locktime": "0", - "threshold": "1", - "addresses": ["P-avax18jma8ppw3nhx5r4ap8clazz0dps7rv5ukulre5"] - }, - "potentialReward": "11743144774" - } - ] - } - ] - }, - "id": 1 + "startTime": "1731445206", + "weight": "49463", + "minNonce": "0", + "balance": "1000000000", + "height": "3" + }, + "id": 1 } ``` From fd9e198259c2bce718c5f2998568b9517d1983d8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 12 Nov 2024 16:05:58 -0500 Subject: [PATCH 391/400] nit --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 2aa9d97d63a7..caa0bd3f516b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,7 +4,7 @@ ### APIs -- Added `platform.getSubnetOnlyValidator` API +- Added `platform.getSubnetOnlyValidator` ### Configs From 5953b6b595a626b79194b824fe81cfb17c48b711 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 12 Nov 2024 16:19:12 -0500 Subject: [PATCH 392/400] nit --- tests/e2e/p/l1.go | 58 ++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 254a7369437e..38fff3dccd0c 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -278,13 +278,17 @@ var _ = e2e.DescribePChain("[L1]", func() { sov.Balance = 0 require.Equal( platformvm.SubnetOnlyValidator{ - SubnetID: subnetID, - NodeID: subnetGenesisNode.NodeID, - PublicKey: genesisNodePK, - RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, - DeactivationOwner: &secp256k1fx.OutputOwners{}, - Weight: genesisWeight, - MinNonce: 0, + SubnetID: subnetID, + NodeID: subnetGenesisNode.NodeID, + PublicKey: genesisNodePK, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{ + Addrs: []ids.ShortID{}, + }, + DeactivationOwner: &secp256k1fx.OutputOwners{ + Addrs: []ids.ShortID{}, + }, + Weight: genesisWeight, + MinNonce: 0, }, sov, ) @@ -398,14 +402,18 @@ var _ = e2e.DescribePChain("[L1]", func() { sov.StartTime = 0 require.Equal( platformvm.SubnetOnlyValidator{ - SubnetID: subnetID, - NodeID: subnetRegisterNode.NodeID, - PublicKey: registerNodePK, - RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, - DeactivationOwner: &secp256k1fx.OutputOwners{}, - Weight: registerWeight, - MinNonce: 0, - Balance: 0, + SubnetID: subnetID, + NodeID: subnetRegisterNode.NodeID, + PublicKey: registerNodePK, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{ + Addrs: []ids.ShortID{}, + }, + DeactivationOwner: &secp256k1fx.OutputOwners{ + Addrs: []ids.ShortID{}, + }, + Weight: registerWeight, + MinNonce: 0, + Balance: 0, }, sov, ) @@ -491,14 +499,18 @@ var _ = e2e.DescribePChain("[L1]", func() { sov.StartTime = 0 require.Equal( platformvm.SubnetOnlyValidator{ - SubnetID: subnetID, - NodeID: subnetRegisterNode.NodeID, - PublicKey: registerNodePK, - RemainingBalanceOwner: &secp256k1fx.OutputOwners{}, - DeactivationOwner: &secp256k1fx.OutputOwners{}, - Weight: updatedWeight, - MinNonce: nextNonce, - Balance: 0, + SubnetID: subnetID, + NodeID: subnetRegisterNode.NodeID, + PublicKey: registerNodePK, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{ + Addrs: []ids.ShortID{}, + }, + DeactivationOwner: &secp256k1fx.OutputOwners{ + Addrs: []ids.ShortID{}, + }, + Weight: updatedWeight, + MinNonce: nextNonce, + Balance: 0, }, sov, ) From 963b122469d01260617df141882a461f1960138f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 12 Nov 2024 16:46:08 -0500 Subject: [PATCH 393/400] Add SoV deactivation owner support to the P-chain wallet --- vms/platformvm/client.go | 20 +++++++++- .../txs/executor/standard_tx_executor_test.go | 11 +++--- wallet/chain/p/builder/builder.go | 22 +++++------ wallet/chain/p/signer/signer.go | 2 +- wallet/chain/p/signer/visitor.go | 30 +++++++-------- wallet/chain/p/wallet/backend.go | 28 +++++++------- wallet/chain/p/wallet/backend_visitor.go | 37 ++++++++++++++++++- wallet/subnet/primary/wallet.go | 23 ++++++++++-- 8 files changed, 120 insertions(+), 53 deletions(-) diff --git a/vms/platformvm/client.go b/vms/platformvm/client.go index 0433e22ba10c..f3a5ee0ec014 100644 --- a/vms/platformvm/client.go +++ b/vms/platformvm/client.go @@ -643,7 +643,7 @@ func GetSubnetOwners( ctx context.Context, subnetIDs ...ids.ID, ) (map[ids.ID]fx.Owner, error) { - subnetOwners := map[ids.ID]fx.Owner{} + subnetOwners := make(map[ids.ID]fx.Owner, len(subnetIDs)) for _, subnetID := range subnetIDs { subnetInfo, err := c.GetSubnet(ctx, subnetID) if err != nil { @@ -657,3 +657,21 @@ func GetSubnetOwners( } return subnetOwners, nil } + +// GetDeactivationOwners returns a map of validation ID to subnet-only +// validation deactivation owner +func GetDeactivationOwners( + c Client, + ctx context.Context, + validationIDs ...ids.ID, +) (map[ids.ID]fx.Owner, error) { + deactivationOwners := make(map[ids.ID]fx.Owner, len(validationIDs)) + for _, validationID := range validationIDs { + sov, _, err := c.GetSubnetOnlyValidator(ctx, validationID) + if err != nil { + return nil, err + } + deactivationOwners[validationID] = sov.DeactivationOwner + } + return deactivationOwners, nil +} diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 853a5f861c76..376a31499dd7 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -3125,20 +3125,19 @@ func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { nil, // chainIDs ) - message := test.message - if message == nil { - message = warpMessage.Bytes() - } registerSubnetValidatorTx, err := wallet.IssueRegisterSubnetValidatorTx( test.balance, pop.ProofOfPossession, - message, + warpMessage.Bytes(), test.builderOptions..., ) require.NoError(err) + unsignedTx := registerSubnetValidatorTx.Unsigned.(*txs.RegisterSubnetValidatorTx) + if test.message != nil { + unsignedTx.Message = test.message + } if test.updateTx != nil { - unsignedTx := registerSubnetValidatorTx.Unsigned.(*txs.RegisterSubnetValidatorTx) test.updateTx(unsignedTx) } diff --git a/wallet/chain/p/builder/builder.go b/wallet/chain/p/builder/builder.go index 8ca24ba8f7d9..cb58b2dc2485 100644 --- a/wallet/chain/p/builder/builder.go +++ b/wallet/chain/p/builder/builder.go @@ -301,7 +301,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, ownerID ids.ID) (fx.Owner, error) } type builder struct { @@ -458,7 +458,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 } @@ -516,7 +516,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 } @@ -619,7 +619,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 } @@ -750,7 +750,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 } @@ -827,7 +827,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 } @@ -1215,7 +1215,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 } @@ -1754,12 +1754,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 c595ab82a30a..a155b351faa3 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") @@ -64,7 +64,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 } @@ -85,7 +85,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 } @@ -127,7 +127,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 } @@ -140,7 +140,7 @@ func (s *visitor) TransformSubnetTx(tx *txs.TransformSubnetTx) 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 } @@ -169,7 +169,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 } @@ -190,7 +190,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 } @@ -269,17 +269,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, ) } @@ -288,8 +288,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..da6b31083f1a 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 or validationID -> 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 fdd3c1a0e40a..8df2ba7962bf 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, ) @@ -108,7 +112,7 @@ func (b *backendVisitor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionlessD } func (b *backendVisitor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwnershipTx) error { - b.b.setSubnetOwner( + b.b.setOwner( tx.Subnet, tx.Owner, ) @@ -120,10 +124,39 @@ func (b *backendVisitor) BaseTx(tx *txs.BaseTx) error { } func (b *backendVisitor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { + for i, vdr := range tx.Validators { + b.b.setOwner( + tx.Subnet.Append(uint32(i)), + &secp256k1fx.OutputOwners{ + Threshold: vdr.DeactivationOwner.Threshold, + Addrs: vdr.DeactivationOwner.Addresses, + }, + ) + } return b.baseTx(&tx.BaseTx) } 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) } diff --git a/wallet/subnet/primary/wallet.go b/wallet/subnet/primary/wallet.go index 2b35c944c39c..55dc72f06146 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/platformvm/fx" "github.com/ava-labs/avalanchego/wallet/chain/c" "github.com/ava-labs/avalanchego/wallet/chain/p" "github.com/ava-labs/avalanchego/wallet/chain/x" @@ -73,9 +74,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,8 +109,21 @@ func MakeWallet(ctx context.Context, config *WalletConfig) (Wallet, error) { if err != nil { return nil, err } + deactivationOwners, err := platformvm.GetDeactivationOwners(avaxState.PClient, ctx, config.ValidationIDs...) + if err != nil { + return nil, err + } + + owners := make(map[ids.ID]fx.Owner, len(subnetOwners)+len(deactivationOwners)) + for id, owner := range subnetOwners { + owners[id] = owner + } + for id, owner := range deactivationOwners { + owners[id] = owner + } + pUTXOs := common.NewChainUTXOs(constants.PlatformChainID, avaxState.UTXOs) - pBackend := pwallet.NewBackend(avaxState.PCTX, pUTXOs, subnetOwners) + pBackend := pwallet.NewBackend(avaxState.PCTX, pUTXOs, owners) pClient := p.NewClient(avaxState.PClient, pBackend) pBuilder := pbuilder.New(avaxAddrs, avaxState.PCTX, pBackend) pSigner := psigner.New(config.AVAXKeychain, pBackend) From 1427525b3d32a7bbdfa52a1b8346c49938bb42ac Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 12 Nov 2024 16:55:41 -0500 Subject: [PATCH 394/400] reduce diff --- tests/e2e/p/l1.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index 99cf7efd3b79..74f129f3a58d 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -603,6 +603,30 @@ var _ = e2e.DescribePChain("[L1]", func() { }) }) + tc.By("verifying the SoV can be fetched", func() { + sov, _, err := pClient.GetSubnetOnlyValidator(tc.DefaultContext(), registerValidationID) + require.NoError(err) + + sov.StartTime = 0 + require.Equal( + platformvm.SubnetOnlyValidator{ + SubnetID: subnetID, + NodeID: subnetRegisterNode.NodeID, + PublicKey: registerNodePK, + RemainingBalanceOwner: &secp256k1fx.OutputOwners{ + Addrs: []ids.ShortID{}, + }, + DeactivationOwner: &secp256k1fx.OutputOwners{ + Addrs: []ids.ShortID{}, + }, + Weight: updatedWeight, + MinNonce: nextNonce, + Balance: 0, + }, + sov, + ) + }) + tc.By("fetching the validator weight change attestation", func() { unsignedSubnetValidatorWeight := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( networkID, From 79cd3be175d62a9358b12aea04c5134b2cc50da8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 12 Nov 2024 16:58:38 -0500 Subject: [PATCH 395/400] fix merge --- tests/e2e/p/l1.go | 91 ----------------------------------------------- 1 file changed, 91 deletions(-) diff --git a/tests/e2e/p/l1.go b/tests/e2e/p/l1.go index e1042d83bd24..38fff3dccd0c 100644 --- a/tests/e2e/p/l1.go +++ b/tests/e2e/p/l1.go @@ -535,97 +535,6 @@ var _ = e2e.DescribePChain("[L1]", func() { }) }) - var nextNonce uint64 - setWeight := func(validationID ids.ID, weight uint64) { - tc.By("creating the unsigned SubnetValidatorWeightMessage") - unsignedSubnetValidatorWeight := must[*warp.UnsignedMessage](tc)(warp.NewUnsignedMessage( - networkID, - chainID, - must[*payload.AddressedCall](tc)(payload.NewAddressedCall( - address, - must[*warpmessage.SubnetValidatorWeight](tc)(warpmessage.NewSubnetValidatorWeight( - validationID, - nextNonce, - weight, - )).Bytes(), - )).Bytes(), - )) - - tc.By("sending the request to sign the warp message", func() { - setSubnetValidatorWeightRequest, err := wrapWarpSignatureRequest( - unsignedSubnetValidatorWeight, - nil, - ) - require.NoError(err) - - require.True(genesisPeer.Send(tc.DefaultContext(), setSubnetValidatorWeightRequest)) - }) - - tc.By("getting the signature response") - 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") - setSubnetValidatorWeight, err := warp.NewMessage( - unsignedSubnetValidatorWeight, - &warp.BitSetSignature{ - Signers: set.NewBits(0).Bytes(), // [signers] has weight from the genesis peer - Signature: ([bls.SignatureLen]byte)( - bls.SignatureToBytes(setSubnetValidatorWeightSignature), - ), - }, - ) - require.NoError(err) - - tc.By("issuing a SetSubnetValidatorWeightTx", func() { - _, err := pWallet.IssueSetSubnetValidatorWeightTx( - setSubnetValidatorWeight.Bytes(), - ) - require.NoError(err) - }) - - nextNonce++ - } - - tc.By("increasing the weight of the validator", func() { - setWeight(registerValidationID, 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, - }, - }) - }) - }) - - tc.By("advancing the proposervm P-chain height", advanceProposerVMPChainHeight) - - tc.By("removing the registered validator", func() { - setWeight(registerValidationID, 0) - }) - - 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, - }, - }) - }) - }) - genesisPeerMessages.Close() genesisPeer.StartClose() require.NoError(genesisPeer.AwaitClosed(tc.DefaultContext())) From c7842c0b2acd6685cc4ab3283b9535c4b760e7f1 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 12 Nov 2024 17:09:17 -0500 Subject: [PATCH 396/400] fix merge --- wallet/chain/p/wallet/backend_visitor.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/wallet/chain/p/wallet/backend_visitor.go b/wallet/chain/p/wallet/backend_visitor.go index e881f1a5d19c..8df2ba7962bf 100644 --- a/wallet/chain/p/wallet/backend_visitor.go +++ b/wallet/chain/p/wallet/backend_visitor.go @@ -164,10 +164,6 @@ func (b *backendVisitor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidatorWe 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.b.removeUTXOs( b.ctx, From f7c3e7177a05a998421fc21af44b5b95ecc40ddf Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 12 Nov 2024 19:28:09 -0500 Subject: [PATCH 397/400] nit --- vms/platformvm/block/builder/helpers_test.go | 1 + vms/platformvm/block/executor/helpers_test.go | 1 + vms/platformvm/block/executor/verifier_test.go | 3 +++ vms/platformvm/txs/executor/helpers_test.go | 1 + .../txs/executor/standard_tx_executor_test.go | 6 ++++++ vms/platformvm/txs/txstest/wallet.go | 16 +++++++++++++++- vms/platformvm/vm_test.go | 1 + 7 files changed, 28 insertions(+), 1 deletion(-) diff --git a/vms/platformvm/block/builder/helpers_test.go b/vms/platformvm/block/builder/helpers_test.go index 2d20ce56dc1a..194116273a0c 100644 --- a/vms/platformvm/block/builder/helpers_test.go +++ b/vms/platformvm/block/builder/helpers_test.go @@ -218,6 +218,7 @@ func newWallet(t testing.TB, e *environment, c walletConfig) wallet.Wallet { e.state, secp256k1fx.NewKeychain(c.keys...), c.subnetIDs, + nil, // validationIDs []ids.ID{e.ctx.CChainID, e.ctx.XChainID}, ) } diff --git a/vms/platformvm/block/executor/helpers_test.go b/vms/platformvm/block/executor/helpers_test.go index 14554e248b5d..cef542533ded 100644 --- a/vms/platformvm/block/executor/helpers_test.go +++ b/vms/platformvm/block/executor/helpers_test.go @@ -224,6 +224,7 @@ func newWallet(t testing.TB, e *environment, c walletConfig) wallet.Wallet { e.state, secp256k1fx.NewKeychain(c.keys...), c.subnetIDs, + nil, // validationIDs []ids.ID{e.ctx.CChainID, e.ctx.XChainID}, ) } diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index 16b129b36882..f1715842aedf 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -197,6 +197,7 @@ func TestVerifierVisitAtomicBlock(t *testing.T) { verifier.state, secp256k1fx.NewKeychain(genesis.EWOQKey), nil, // subnetIDs + nil, // validationIDs nil, // chainIDs ) exportedOutput = &avax.TransferableOutput{ @@ -346,6 +347,7 @@ func TestVerifierVisitStandardBlock(t *testing.T) { verifier.state, secp256k1fx.NewKeychain(genesis.EWOQKey), nil, // subnetIDs + nil, // validationIDs []ids.ID{ctx.XChainID}, // Read the UTXO to import ) initialTimestamp = verifier.state.GetTimestamp() @@ -1098,6 +1100,7 @@ func TestBlockExecutionWithComplexity(t *testing.T) { verifier.state, secp256k1fx.NewKeychain(genesis.EWOQKey), nil, // subnetIDs + nil, // validationIDs nil, // chainIDs ) diff --git a/vms/platformvm/txs/executor/helpers_test.go b/vms/platformvm/txs/executor/helpers_test.go index 3823355795d7..7c1dbdc1e005 100644 --- a/vms/platformvm/txs/executor/helpers_test.go +++ b/vms/platformvm/txs/executor/helpers_test.go @@ -192,6 +192,7 @@ func newWallet(t testing.TB, e *environment, c walletConfig) wallet.Wallet { e.state, secp256k1fx.NewKeychain(c.keys...), c.subnetIDs, + nil, // validationIDs c.chainIDs, ) } diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 376a31499dd7..350267ad23ca 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -2383,6 +2383,7 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { baseState, secp256k1fx.NewKeychain(genesistest.DefaultFundedKeys...), nil, // subnetIDs + nil, // validationIDs nil, // chainIDs ) flowChecker = utxo.NewVerifier( @@ -2562,6 +2563,7 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { baseState, secp256k1fx.NewKeychain(genesistest.DefaultFundedKeys...), []ids.ID{subnetID}, + nil, // validationIDs nil, // chainIDs ) chainID = ids.GenerateTestID() @@ -2707,6 +2709,7 @@ func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { baseState, secp256k1fx.NewKeychain(genesistest.DefaultFundedKeys...), nil, // subnetIDs + nil, // validationIDs nil, // chainIDs ) flowChecker = utxo.NewVerifier( @@ -3122,6 +3125,7 @@ func TestStandardExecutorRegisterSubnetValidatorTx(t *testing.T) { baseState, secp256k1fx.NewKeychain(genesistest.DefaultFundedKeys...), nil, // subnetIDs + nil, // validationIDs nil, // chainIDs ) @@ -3238,6 +3242,7 @@ func TestStandardExecutorSetSubnetValidatorWeightTx(t *testing.T) { baseState, secp256k1fx.NewKeychain(genesistest.DefaultFundedKeys...), nil, // subnetIDs + nil, // validationIDs nil, // chainIDs ) flowChecker = utxo.NewVerifier( @@ -3633,6 +3638,7 @@ func TestStandardExecutorSetSubnetValidatorWeightTx(t *testing.T) { baseState, secp256k1fx.NewKeychain(genesistest.DefaultFundedKeys...), nil, // subnetIDs + nil, // validationIDs nil, // chainIDs ) diff --git a/vms/platformvm/txs/txstest/wallet.go b/vms/platformvm/txs/txstest/wallet.go index f26f2d612aef..2de042d402ef 100644 --- a/vms/platformvm/txs/txstest/wallet.go +++ b/vms/platformvm/txs/txstest/wallet.go @@ -18,6 +18,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/fx" "github.com/ava-labs/avalanchego/vms/platformvm/state" "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/chain/p/builder" "github.com/ava-labs/avalanchego/wallet/chain/p/signer" @@ -32,6 +33,7 @@ func NewWallet( state state.State, kc *secp256k1fx.Keychain, subnetIDs []ids.ID, + validationIDs []ids.ID, chainIDs []ids.ID, ) wallet.Wallet { var ( @@ -74,12 +76,24 @@ func NewWallet( } } - owners := make(map[ids.ID]fx.Owner, len(subnetIDs)) + owners := make(map[ids.ID]fx.Owner, len(subnetIDs)+len(validationIDs)) for _, subnetID := range subnetIDs { owner, err := state.GetSubnetOwner(subnetID) require.NoError(err) owners[subnetID] = owner } + for _, validationID := range validationIDs { + sov, err := state.GetSubnetOnlyValidator(validationID) + require.NoError(err) + + var owner message.PChainOwner + _, err = txs.Codec.Unmarshal(sov.DeactivationOwner, &owner) + require.NoError(err) + owners[validationID] = &secp256k1fx.OutputOwners{ + Threshold: owner.Threshold, + Addrs: owner.Addresses, + } + } builderContext := newContext(ctx, config, state) backend := wallet.NewBackend( diff --git a/vms/platformvm/vm_test.go b/vms/platformvm/vm_test.go index e8c83ba3350e..3a9f2f695817 100644 --- a/vms/platformvm/vm_test.go +++ b/vms/platformvm/vm_test.go @@ -241,6 +241,7 @@ func newWallet(t testing.TB, vm *VM, c walletConfig) wallet.Wallet { vm.state, secp256k1fx.NewKeychain(c.keys...), c.subnetIDs, + nil, // validationIDs []ids.ID{vm.ctx.CChainID, vm.ctx.XChainID}, ) } From 039d5393b1bad845eaf43b93b03518ec8ad55502 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 12 Nov 2024 19:31:58 -0500 Subject: [PATCH 398/400] Rename error to be more generic --- vms/platformvm/txs/executor/create_chain_test.go | 4 ++-- vms/platformvm/txs/executor/proposal_tx_executor_test.go | 4 ++-- vms/platformvm/txs/executor/standard_tx_executor_test.go | 8 ++++---- vms/platformvm/txs/executor/subnet_tx_verification.go | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/vms/platformvm/txs/executor/create_chain_test.go b/vms/platformvm/txs/executor/create_chain_test.go index f271b81dbebf..c930f7723e4e 100644 --- a/vms/platformvm/txs/executor/create_chain_test.go +++ b/vms/platformvm/txs/executor/create_chain_test.go @@ -58,7 +58,7 @@ func TestCreateChainTxInsufficientControlSigs(t *testing.T) { tx, stateDiff, ) - require.ErrorIs(err, errUnauthorizedSubnetModification) + require.ErrorIs(err, errUnauthorizedModification) } // Ensure Execute fails when an incorrect control signature is given @@ -101,7 +101,7 @@ func TestCreateChainTxWrongControlSig(t *testing.T) { tx, stateDiff, ) - require.ErrorIs(err, errUnauthorizedSubnetModification) + require.ErrorIs(err, errUnauthorizedModification) } // Ensure Execute fails when the Subnet the blockchain specifies as diff --git a/vms/platformvm/txs/executor/proposal_tx_executor_test.go b/vms/platformvm/txs/executor/proposal_tx_executor_test.go index d6759ee54767..70d2f3a4aa67 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor_test.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor_test.go @@ -697,7 +697,7 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { onCommitState, onAbortState, ) - require.ErrorIs(err, errUnauthorizedSubnetModification) + require.ErrorIs(err, errUnauthorizedModification) } { @@ -737,7 +737,7 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { onCommitState, onAbortState, ) - require.ErrorIs(err, errUnauthorizedSubnetModification) + require.ErrorIs(err, errUnauthorizedModification) } { diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 853a5f861c76..6ce54be5e738 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -793,7 +793,7 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { tx, onAcceptState, ) - require.ErrorIs(err, errUnauthorizedSubnetModification) + require.ErrorIs(err, errUnauthorizedModification) } { @@ -830,7 +830,7 @@ func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { tx, onAcceptState, ) - require.ErrorIs(err, errUnauthorizedSubnetModification) + require.ErrorIs(err, errUnauthorizedModification) } { @@ -1962,7 +1962,7 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { } return env.unsignedTx, e }, - expectedErr: errUnauthorizedSubnetModification, + expectedErr: errUnauthorizedModification, }, { name: "flow checker failed", @@ -2463,7 +2463,7 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { }) return nil }, - expectedErr: errUnauthorizedSubnetModification, + expectedErr: errUnauthorizedModification, }, { name: "invalid if subnet is transformed", diff --git a/vms/platformvm/txs/executor/subnet_tx_verification.go b/vms/platformvm/txs/executor/subnet_tx_verification.go index f58c230d5e37..3b4140235b23 100644 --- a/vms/platformvm/txs/executor/subnet_tx_verification.go +++ b/vms/platformvm/txs/executor/subnet_tx_verification.go @@ -16,9 +16,9 @@ import ( ) var ( - errWrongNumberOfCredentials = errors.New("should have the same number of credentials as inputs") - errIsImmutable = errors.New("is immutable") - errUnauthorizedSubnetModification = errors.New("unauthorized subnet modification") + errWrongNumberOfCredentials = errors.New("should have the same number of credentials as inputs") + errIsImmutable = errors.New("is immutable") + errUnauthorizedModification = errors.New("unauthorized modification") ) // verifyPoASubnetAuthorization carries out the validation for modifying a PoA @@ -93,7 +93,7 @@ func verifyAuthorization( authCred := tx.Creds[baseTxCredsLen] if err := fx.VerifyPermission(tx.Unsigned, auth, authCred, owner); err != nil { - return nil, fmt.Errorf("%w: %w", errUnauthorizedSubnetModification, err) + return nil, fmt.Errorf("%w: %w", errUnauthorizedModification, err) } return tx.Creds[:baseTxCredsLen], nil From e485bd0d40c032c61343fa2dea9cec78a83285d8 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 12 Nov 2024 20:06:46 -0500 Subject: [PATCH 399/400] Add concurrent reads --- vms/platformvm/state/state.go | 4 ++++ vms/platformvm/vm.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index d2dfd34e47a8..58afa8498ddd 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -816,6 +816,7 @@ func (s *state) GetExpiryIterator() (iterator.Iterator[ExpiryEntry], error) { ), nil } +// HasExpiry allows for concurrent reads. func (s *state) HasExpiry(entry ExpiryEntry) (bool, error) { if has, modified := s.expiryDiff.modified[entry]; modified { return has, nil @@ -859,6 +860,7 @@ func (s *state) WeightOfSubnetOnlyValidators(subnetID ids.ID) (uint64, error) { return weight, nil } +// GetSubnetOnlyValidator allows for concurrent reads. func (s *state) GetSubnetOnlyValidator(validationID ids.ID) (SubnetOnlyValidator, error) { if sov, modified := s.sovDiff.modified[validationID]; modified { if sov.isDeleted() { @@ -1047,6 +1049,7 @@ func (s *state) SetSubnetOwner(subnetID ids.ID, owner fx.Owner) { s.subnetOwners[subnetID] = owner } +// GetSubnetConversion allows for concurrent reads. func (s *state) GetSubnetConversion(subnetID ids.ID) (SubnetConversion, error) { if c, ok := s.subnetConversions[subnetID]; ok { return c, nil @@ -1267,6 +1270,7 @@ func (s *state) GetEtnaHeight() (uint64, error) { return database.GetUInt64(s.singletonDB, EtnaHeightKey) } +// GetTimestamp allows for concurrent reads. func (s *state) GetTimestamp() time.Time { return s.timestamp } diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index 09026085e229..90540203e8d9 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -193,7 +193,7 @@ func (vm *VM) Initialize( mempool, txExecutorBackend.Config.PartialSyncPrimaryNetwork, appSender, - &chainCtx.Lock, + chainCtx.Lock.RLocker(), vm.state, chainCtx.WarpSigner, registerer, From d9b04ba517a5727d72e3091412504aaa3dd54de0 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 13 Nov 2024 09:18:05 -0500 Subject: [PATCH 400/400] Address PR comments --- vms/platformvm/client.go | 11 +++++++---- vms/platformvm/service.go | 6 ++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/vms/platformvm/client.go b/vms/platformvm/client.go index 0433e22ba10c..3a3469fff626 100644 --- a/vms/platformvm/client.go +++ b/vms/platformvm/client.go @@ -341,7 +341,7 @@ type SubnetOnlyValidator struct { StartTime uint64 Weight uint64 MinNonce uint64 - // Balance is the remaining amount of AVAX this SoV has to pay the + // Balance is the remaining amount of AVAX this SoV has for paying the // continuous fee. Balance uint64 } @@ -352,9 +352,12 @@ func (c *client) GetSubnetOnlyValidator( options ...rpc.Option, ) (SubnetOnlyValidator, uint64, error) { res := &GetSubnetOnlyValidatorReply{} - err := c.requester.SendRequest(ctx, "platform.getSubnetOnlyValidator", &GetSubnetOnlyValidatorArgs{ - ValidationID: validationID, - }, res, options...) + err := c.requester.SendRequest(ctx, "platform.getSubnetOnlyValidator", + &GetSubnetOnlyValidatorArgs{ + ValidationID: validationID, + }, + res, options..., + ) if err != nil { return SubnetOnlyValidator{}, 0, err } diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index fc9f4b9ab4ac..f48421c288a3 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -984,8 +984,9 @@ type GetSubnetOnlyValidatorReply struct { StartTime avajson.Uint64 `json:"startTime"` Weight avajson.Uint64 `json:"weight"` MinNonce avajson.Uint64 `json:"minNonce"` - // Balance is the remaining amount of AVAX this SoV has to pay the - // continuous fee, according to the last accepted state. + // Balance is the remaining amount of AVAX this SoV has for paying the + // continuous fee, according to the last accepted state. If the validator is + // inactive, the balance will be 0. Balance avajson.Uint64 `json:"balance"` // Height is the height of the last accepted block Height avajson.Uint64 `json:"height"` @@ -996,6 +997,7 @@ func (s *Service) GetSubnetOnlyValidator(r *http.Request, args *GetSubnetOnlyVal s.vm.ctx.Log.Debug("API called", zap.String("service", "platform"), zap.String("method", "getSubnetOnlyValidator"), + zap.Stringer("validationID", args.ValidationID), ) s.vm.ctx.Lock.Lock()